Restricting the number of times a function recurses in php codeigniter - php

I want to generate a multilevel hierarchical select option to select parent or any of its child categories in codeigniter. I want to limit the hierarchy level to 3. i.e the option must show parent, its first level child and its second level child with child category indented to the right of parent. I've no problem going through all the hierarchy level. But I can't generate the select option upto a particular hierarchy level. Three levels of hierarchy is what I need in my case. Can anyone please help me.
Here is what I tried so far:
<?php
$category = $this->get_category();
function get_category() {
$this->db->order_by('parent_id', 'DESC');
$this->db->select('id, name, parent_id');
return $this->db->get('tbl_job_category')->result_array();
}
/*
*$category contains the following json data obtained from DB
$category = [{"id":"20","name":"MVC","parent_id":"16"},
{"id":"16","name":"Spring","parent_id":"14"},
{"id":"12","name":"Car Driver","parent_id":"13"},
{"id":"6","name":"Electrical","parent_id":"5"},
{"id":"3","name":"Civil","parent_id":"5"},
{"id":"14","name":"java","parent_id":"2"},
{"id":"15","name":"javascript","parent_id":"2"},
{"id":"17","name":"Computer Operator","parent_id":"1"},
{"id":"2","name":"Programming","parent_id":"1"},
{"id":"4","name":"Networking","parent_id":"1"},
{"id":"11","name":"Hardware","parent_id":"1"},
{"id":"13","name":"Driver","parent_id":"0"},
{"id":"5","name":"Engineering","parent_id":"0"},
{"id":"19","name":"Economics","parent_id":"0"},
{"id":"1","name":"Information Technology","parent_id":"0"}];
*/
$category_options = $this->multilevel_select($category);
function multilevel_select($array,$parent_id = 0,$parents = array()) {
static $i=0;
if($parent_id==0)
{
foreach ($array as $element) {
if (($element['parent_id'] != 0) && !in_array($element['parent_id'],$parents)) {
$parents[] = $element['parent_id'];
}
}
}
$menu_html = '';
foreach($array as $element){
if($element['parent_id']==$parent_id){
$menu_html .= '<option>';
for($j=0; $j<$i; $j++) {
$menu_html .= '—';
}
$menu_html .= $element['name'].'</option>';
if(in_array($element['id'], $parents)){
$i++;
$menu_html .= $this->multilevel_select($array, $element['id'], $parents);
}
}
}
$i--;
return $menu_html;
}
echo $category_options;

Limiting the recursion involves three steps :
Initializing a counter variable passed as parameter to the recursive function.
Testing the counter value against the desired boundary prior to recursing
Passing an incremented counter value in case of recursive call.
In your case that would be the $level variable below :
function multilevel_select($array,$parent_id = 0,$parents = array(), $level=0) {
// ....
if(in_array($element['id'], $parents) && $level < 2){ // your boundary here, 2 for third nesting level from root
$i++;
$menu_html .= $this->multilevel_select($array, $element['id'], $parents, $level+1);
}
}
}
$i--;
return $menu_html;
}

Related

PHP mysql Tree Child count and List All Child node level wise

I have a tree like
and I want the output for requeted ID for example Admin
my table structure is
I have a method that returns Child count level wise but I also want to return the child list level-wise with child count level-wise like 2nd image output required.
function childCountLevelWise($conn,$ID, $level){
if ($level>14){
$count = array(0=>0);
return $count;
}
$sql="select * from user_my_tree t where t.parent_ID=".$ID;
$result=returnResults($conn,$sql);
if ($result==null){
$count = array(0=>0);
}
else{
$count = array(0=>0);
foreach($result as $key=>$row)
{
$count[0]++;
$children=childCountLevelWise($conn,$row['ID'], $level+1);
$index=1;
foreach ($children as $child)
{
if ($child==0)
continue;
if (isset($count[$index]))
$count[$index] += $child;
else
$count[$index] = $child;
$index++;
}
}
}
return $count;
}
It sounds like you just want a way of counting nested sets, I can reproduce your example without using a database with this code:
function returnResults($parent)
{
switch ($parent) {
case 'root':
return ['vijay', 'suresh', 'mukesh'];
case 'vijay':
return ['manish', 'rohan', 'manu'];
case 'manish':
return ['rinku', 'raja', 'vijay2'];
default:
return [];
}
}
function childCountLevelWise($parent, $level, &$result)
{
$result[$level] = empty($result[$level]) ? [] : $result[$level]; // init array
if ($level > 14) {
return; // ignore levels over 14
}
$levelResults = returnResults($parent); // get results for this parent
$result[$level] = array_merge($result[$level], $levelResults); // add to results for this level
foreach ($levelResults as $child) {
childCountLevelWise($child, $level + 1, $result); // check for each child at this level
}
}
And calling it and printing the results with this code
childCountLevelWise('root', 0, $result);
// print result
foreach ($result as $level => $people) {
if (!empty($people)) {
printf('Result for level %d: %s', $level, implode(',', $people));
echo "\n";
}
}
Will result in:
Result for level 0: vijay,suresh,mukesh
Result for level 1: manish,rohan,manu
Result for level 2: rinku,raja,vijay2
From there I think it should be simple enough to modify the returnResults function in my example to query the database, although if you're using this on a lot of results you might want to consider the performance costs of this. There are good solutions to having tree structures in the database already, such as the Nested Set in Doctrine.

depth limited search - Not giving me the right result due to the depth I guess?

/*for example using a graph of this nature
----1-----0
/\
---2----3----1
/\ /\
--4--5--6--7---2
at level 2 it will return 1,2,4,5,3 instead of 1,2,4,5,3,6,7
this is the main function to loop through the graph*/
public function set_data()
{
$this->data_type();
while(count($this->_open) > 0 && $this->_state !== 'success')
{
$depth = $this->_depth;
$current = array_pop($this->_open);
if($current == $this->_goal){
$this->_state = 'success';
}
if ($depth < $this->_limit) {
$this->set_children($current, $depth);
}
else{
$this->_state = 'cutoff';
}
array_push($this->_close, $current);
}
}
// the function to generate the children
public function set_children($current, $depth)
{
if(isset($this->_graph["$current"])){
foreach ($this->_graph["$current"] as $value)
{
//implementing stack - LIFO
array_push($this->_open, array_pop($this->_graph["$current"]));
}
$depth = $depth+1;
$this->_depth = $depth;
}
}
After a long while I finally got a solution. I made the $depth an array to hold the depth of corresponding node.
First initialization
$this->_depth = [0];
Second, current depth of the current node
$depth = array_pop($this->_depth);
Third, create the depth for each child node
array_push($this->_depth, $depth++);
$depth--;

concat tree hierarchie in a recursive PHP function

I have a user table with hierachical users. So users can have a parent user. I am trying to return an array of all child user ids of a certain user.
My function returns "null". What's wrong?
public function userDownline($userid, $result = array()) {
$dbconn = $this->DBase();
$children = $dbconn->GetAll('SELECT id FROM users WHERE parent=' . (int)$userid);
if(count($children) > 0) {
foreach($children As $k=>$v) {
if(!in_array($v['id'], $result)) $result[] = $v['id'];
$this->userDownline($v['id'], $result);
}
} else {
return $result;
}
}
Of course it will return null, because you are in block if(count($children)) and there is no return from this.
I think you have to do something like this:
<?php
public function userDownline($userid, &$result = array())
{
$dbconn = $this->DBase();
$children = $dbconn->GetAll('SELECT id FROM users WHERE parent=' . (int)$userid);
if (count($children) > 0) {
foreach ($children As $k => $v) {
if (!in_array($v['id'], $result)) $result[] = $v['id'];
$this->userDownline($v['id'], $result);
}
}
return $result;
}
I added reference in function signature and move return out of the conditional block.
But this is really totaly inefficient way and dangerous(because of - out of memory, too many nesting level and other exceptions).
There are 2 better ways:
Use https://neo4j.com/ - Graph Database - best option for your task.
If you still wanna use only Sql DB - read about Nested set Model http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

Get Nested Level from Recursive Menu

I was looking at implementing the recursive function from this SO item: PHP: nested menu with a recursive function, expand only some nodes (not all the tree) to build a right click menu on my application and need to get the nested level. I am trying to think through the workflow here but can't think of where to put my increment by one and reset it to end up with variables such as:
$nestedLevel = 1; // main item
$nestedLevel = 2; // sub menu
$nestedLevel = 2; // sub-sub menu
I would like to use that to setup some if statements to I can handle each level differently. the Main level will be a simple list, Level 2 would be a horizontal button group, and the third will be simple lists assigned to tabs from the button group. Here is what I have:
function recursive($parent, $array) {
$has_children = false;
foreach($array as $key => $value) {
if ($value['menu_parent_id'] == $parent) {
if ($has_children === false && $parent) {
$has_children = true;
echo '<ul>' ."\n";
}
echo '<li>' . "\n";
echo '' . $value['name'] . '' . " \n";
echo "\n";
recursive($key, $array);
echo "</li>\n";
}
}
if ($has_children === true && $parent) echo "</ul>\n";
}
In the end it will look something like this (with some more styling of course):

How do I pick only the first few items in an array?

Here's something simple for someone to answer for me. I've tried searching but I don't know what I'm looking for really.
I have an array from a JSON string, in PHP, of cast and crew members for a movie.
Here I am pulling out only the people with the job name 'Actor'
foreach ($movies[0]->cast as $cast) {
if ($cast->job == 'Actor') {
echo '<p>' . $cast->name . ' - ' . $cast->character . '</p>';
}
}
The problem is, I would like to be able to limit how many people with the job name 'Actor' are pulled out. Say, the first 3.
So how would I pick only the first 3 of these people from this array?
OK - this is a bit of over-kill for this problem, but perhaps it serves some educational purposes. PHP comes with a set of iterators that may be used to abstract iteration over a given set of items.
class ActorIterator extends FilterIterator {
public function accept() {
return $this->current()->job == 'Actor';
}
}
$maxCount = 3;
$actors = new LimitIterator(
new ActorIterator(
new ArrayIterator($movies[0]->cast)
),
0,
$maxCount
);
foreach ($actors as $actor) {
echo /*... */;
}
By extending the abstract class FilterIterator we are able to define a filter that returns only the actors from the given list. LimitIterator allows you to limit the iteration to a given set and the ArrayIterator is a simple helper to make native arrays compatible with the Iterator interface. Iterators allow the developer to build chains that define the iteration process which makes them extremely flexible and powerful.
As I said in the introduction: the given problem can be solved easily without this Iterator stuff, but it provides the developer with some extended options and enables code-reuse. You could, for example, enhance the ActorIterator to some CastIterator that allows you to pass the cast type to filter for in the constructor.
Use a variable called $num_actors to track how many you've already counted, and break out of the loop once you get to 3.
$num_actors = 0;
foreach ( $movies[0]->cast as $cast ) {
if ( $cast->job == 'Actor' ) {
echo '...';
$num_actors += 1;
if ( $num_actors == 3 )
break;
}
}
$actors=array_filter($movies[0]->cast, function ($v) {
return $v->job == 'Actor';
});
$first3=array_slice($actors, 0, 3);
or even
$limit=3;
$actors=array_filter($movies[0]->cast, function ($v) use (&$limit) {
if ($limit>0 && $v->job == 'Actor') {
$limit--;
return true;
}
return false;
});
Add a counter and an if statement.
$count = 0;
foreach ($movies[0]->cast as $cast)
{
if ($cast->job == 'Actor')
{
echo '<p>' . $cast->name . ' - ' . $cast-character . '</p>';
if($count++ >= 3)
break;
}
}
$limit = 3;
$count = 0;
foreach ($movies[0]->cast as $cast) {
// You can move the code up here if all you're getting is Actors
if ($cast->job == 'Actor') {
if ($count == $limit) break;// stop the loop
if ($count == $limit) continue;// OR move to next item in loop
$count++;
echo '<p><a href="people.php?id='
. $cast->id
. '">'
. $cast->name
. ' - '
. $cast->character
. '</a></p>';
}
}

Categories