I have a problem with a recursive function that takes too many resources to display a child-parent relation in a dropdown list. For example:
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
I've written some code for a recursive call to the database each time, so that's the reason why my code takes so many resources to run.
Below is my code:
--call recursive
$tmp = $this->get_nav_by_parent(0);
$a_sel = array('' => '-Select-');
$a_sel_cat = array('home' => 'home');
$this->get_child_nav_cat($tmp, 0, $a_sel);
--
public function get_nav_by_parent($parent) {
$all_nav = $this->db
->select('id, title, parent')
->where('parent',$parent)
->order_by('position')
->get('navigation_links')
->result_array();
$a_tmp = array();
foreach($all_nav as $item)
{
if($parent != 0){
$item['title'] = '--' . $item['title'];
}
$a_tmp[] = $item;
}
return $a_tmp;
}
-- Recursive function
public function get_child_nav_cat($a_data, $parent, &$a_sel) {
foreach($a_data as $item) {
$a_sel[$item['page_slug_key']] = $item['title'];
$atmp = $this->get_nav_by_parent($item['id']);
$this->get_child_nav_cat($atmp, $item['id'], $a_sel);
}
return $a_sel;
}
Please give me suggestions for the best solution to display the data as child-parent relationship in select box.
Thanks in advance!
Best way to display parent child relationship is mentain parent and child flag in Database instead of
fetching value using loop.
In your case Home, Home 1 is parent flag and menus belong on child flag.
fetch data from db and your loop look like this:-
$arr = array(0 => array('name' => 'home','parent' => 0),
1 => array('name' => 'menu 1 ','parent' => 1),
2 => array('name' => 'menu 2 ','parent' => 1),
3 => array('name' => 'home 1','parent' => 0),
4 => array('name' => 'menu 3 ','parent' => 2),
5 => array('name' => 'menu 4','parent' => 2)
);
$dd_html = '<select>';
foreach($arr as $k => $v){
if($v['parent'] == 0 )
$dd_html .='<option>'.$v['name'].'</option>';
else
$dd_html .='<option>--'.$v['name'].'</option>';
}
$dd_html .= '</select>';
echo $dd_html;
Output :-
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
Set ParentID=0 for detecting root item
then execute this:
SELECT * FROM table ORDER BY ParentID, ID
Then iterate through result and when ParentID changed, create new level.
Related
I have created a tree level menu but I wanted to separate all sub menus by simply adding -- to every sub menu like
main menu
-- 1st level
---- 2nd level
------ 3rd level
and so on
while doing it in LI it is so easy I can simply put tag before running my function but in select option I am unable to achive my target can anyone here would be able to help me out with this please
function fetch_menu($data) {
foreach($data as $menu) {
echo "<option value='".$menu->cid."'>".$menu->cname."</option>";
if(!empty($menu->sub)) {
fetch_sub_menu($menu->sub);
}
}
}
function fetch_sub_menu($sub_menu, $dash = '--'){
foreach($sub_menu as $menu){
echo "<option value='".$menu->cid."'>".$dash.$menu->cname."</option>";
if(!empty($menu->sub)) {
fetch_sub_menu($menu->sub, '--');
}
}
}
The problem is that while applying the above shown code that dash are not increasing for every 2nd or 3rd level menu
Here is how my data is organized in array form
array (
[cid] => 1,
[cname] => 'Main Menu',
[pcid] => 0,
[sub] => array(
[cid] => 2,
[cname] => '1st Level',
[pcid] => 1,
[sub] => array(
[cid] => 3,
[cname] => '2nd Level',
[pcid] => 2,
[sub] => array(
)
)
)
)
You need to add the new dashes to your $dash variable before recursivly calling the function again. That should work
function fetch_sub_menu($sub_menu, $dash = '--'){
foreach($sub_menu as $menu){
echo "<option value='".$menu->cid."'>".$dash.$menu->cname."</option>";
if(!empty($menu->sub)) {
fetch_sub_menu($menu->sub, $dash.'--'); // <-- adding two dashes to $dash
}
}
}
This question doesn't necessarily have to be related to PHP or MongoDB.
I have MongoDB database with categories collection. Documents inside collection looks like below
{
title : 'Parent 1',
cat_id : '_ABC1',
level : 1
}
{
title : 'Parent 2',
cat_id : '_ABC2'
level : 1
}
{
title : 'Child 1',
cat_id : '_ABC1_CEF1'
level : 2
}
{
title : 'Child 2',
cat_id : '_ABC1_CEF2'
level : 2
}
{
title : 'Child Child 1',
cat_id : '_ABC1_CEF1_GHI1'
level : 3
}
{
title : 'Child Child 2',
cat_id : '_ABC1_CEF1_GHI2'
level : 3
}
Nesting
Now, what I want to do in PHP is get nested array(s) like
$array = array(
array(
'title' => 'Parent 1',
'cat_id' => '_ABC1',
'sub' => array(
'title' => 'Child 1',
'cat_id' => '_ABC1_CEF1',
'sub' => array(
array(
'title' => 'Child Child 1',
'cat_id' => '_ABC1_CEF1_GHI1'
),
array(
'title' => 'Child Child 2',
'cat_id' => '_ABC1_CEF1_GHI2'
)
)
)
),
...
...
)
For that I am using following algorithm (fetch nested level = N)
(N is a fetching parameter number that tells iterator, how deep array has to be fetched)
$array_holder = array();
foreach(MongoGetLevel1Cats as $parent){
$parent['sub'] = array();
foreach(MongoGetLevel2Cats as $child){
$child['sub'] = array();
foreach(MongoGetLevel3Cats as $child_child){
$child_child['sub'] = array();
...
...
array_push($child['sub'], $child_child);
}
array_push($parent['sub'], $child);
}
array_push($array_holder, $parent);
}
Return $array_holder;
As this function will give me the desired result but I don't have control over the deep nested level. If have to fetch nested level of order 100, I have to write foreach loop insider foreach loop for 100 times. Also I don't know performance wise this strategy will be good or not.
Does anybody have any idea how we solve this more strategically? Perhaps with for & foreach loop in combination which doesn't involve nesting foreach loop?
You can use a callback function. For example:
function add_two($result, $how_many_times)
{
$result = $result + 2;//adds 2
echo "current =".$result."<br>";
$how_many_times--;
if ($how_many_times>0)
{
$result = add_two($result, $n);
}
return $result;
}
$resultVar = add_two(5, 5);//should give 15...
echo "resultvar = ".$resultVar."<br>";//outputs: resultvar = 15
In the previous example, I am placing the same function add_two() inside itself and have created a conditional that sees how many iterations I want. Every time I iterate once, I subtract 1 value from $how_many_times. Once $how_man_times == 0, the add_two() function will no longer be called, and the result will have been obtained.
Similarly you can do the same thing with your code. There is no reason for you to have to rewrite the same function all over again if you can just call it inside itself.
Let me know if this is what you were looking for :)
i m trying to build recursive menu using PHP but not succeeding
mysql table
menuid name parentid
and my php code
function generateMenubar()
{
$data = Yii::app()->db->createCommand("select * from menu");
$result = $data->queryAll();
$html = '<ul class = "navigation">';
foreach($result as $row)
{
if($row['parentid'] == "0")
{
$html .= '<li><span>'.$row["menuname"].'</span>';
$menu_id = $row['menuid'];
$html .= $this->generateHTML($result,$menu_id,$html);
}
}
return $html;
}
function generateHTML($result,$menu_id,$html)
{
foreach($result as $row_sub)
{
if($menu_id == $row_sub['parentid'])
{
$html .= '<ul><li><span>'.$row_sub['menuname'].'</span>';
$menu_id = $row_sub['menuid'];
$html .= $this->generateHTML($result,$menu_id,$html);
$html .= '</li>';
}
}
return $html.'</ui>';
}
but this loop is not stopping and generating wrong output. it can have sub levels upto n level. i want to make it dynamic cuz levels may change in future any suggestion ?
Your problem is that you do not have a structured result to iterate over. You might have SubElement 2 > 3 > 4 as your first result but 2 > 3 > 1 as your 5th Result. So you can't just iterate over the result once and build your html.
What you want to do (appart from, switching to nested sets, which is what you REALLY want to do wink) is structure your result first.
Iterate over your result and build a nested array to iterate over recusively to build your HTML. To find "where to put your element" in your recursive array you need to recursively check back with your existing array always as well. IF all you store is id and parent id, how to find out what the root element is before you checked ALL elements right?
So i could write the code to do so, but i rather do not because it would be a horrible solution anyway.
To do so more efficiently it would really help if you do not only store your parentid but a level as well.
Then you could store your elements in a two dimensional array storing all elements for each level and then recursively use that array. i.e.
$navigationTempArray = array();
foreach($fakeMySQLResult as $row)
{
if(!array_key_exists($row['level'], $navigationTempArray )) {
$navigationTempArray[$row['level']] = array();
}
if(!array_key_exists($row['parentid'], $navigationTempArray[$row['level']] )) {
$navigationTempArray[$row['level']][$row['parentid']] = array();
}
$navigationTempArray[$row['level']][$row['parentid']][] = $row;
}
now you have an array like this:
array (
0 => array(
'root' => array(
1 => array('title' => 'Start' ...)
2 => array('title' => 'Team' ...)
3 => array('title' => 'Projects' ...)
)
),
1 => array(
2 => array(
4 => array('title' => 'Development' ...)
5 => array('title' => 'Design' ...)
6 => array('title' => 'Sales' ...)
),
3 => array(
7 => array('title' => 'Mayhem' ...)
8 => array('title' => 'X' ...)
)
),
2 => array(
4 => array(
9 => array('title' => 'PHP' ...)
10 => array('title' => 'MySQL' ...)
)
)
)
Now you can iterate over this array recursively, solving every level for every item to infinity ;-)
function returnSubNavigation($id,$level,$fullNavigationArray) {
$html = '';
if(array_key_exists($level, $fullNavigationArray) && array_key_exists($id, $fullNavigationArray[$level])) {
$html .= '<ul>';
foreach($fullNavigationArray[$level][$id] as $subElement) {
$html .= '<li><span>'.$subElement["menuname"].'</span>';
$html .= returnSubNavigation($subElement['id'], $level+1, $fullNavigationArray);
$html .= '</li>';
}
$html .= '</ul>';
}
return $html;
}
echo returnSubNavigation('root', 0, $navigationTempArray);
Here is an online fiddle kind of thing that proves it works
Some people who do not want to use nested sets often store pathes rather than parent id's.
i.e.:
| id | path |
| 1 | 1 |
| 2 | 2 |
| 3 | 1.1 |
| 4 | 1.2 |
| 5 | 2.1 |
| 6 | 3 |
This is a lot easier (cheaper in terms of performance) to iterate over. You can sort it a lot easier. Still, it brings a lot of problems and restrictions.
I'm using jsTree to view hierarchical data that is stored in a mySQL database as a nested set (left, right, level, etc.). This is working fine, but I need to allow users to import data by uploading a CSV file. When they do so, any existing data in the table will be removed so I don't have to worry about updating the left/right fields.
The data they will be uploading will be in this format:
"Code","Title"
"100","Unit 100"
"200","Unit 200"
"101","Task 101: This is a task"
"102","Task 102: Another task"
"201","Task 201: Yet another"
"300","Unit 300"
"301","Task 301: Another one"
Everything will be a child of a main "Group" that is a level 1 node. All of the "codes" divisible by 100 (ie. 100, 200, 300) will be level 2 (parent nodes.. children of "Group"). All others will be level 3 (child) nodes of their respective parent nodes (ie. 101 and 102 are children of 100, 201 is a child of 200, etc.)
The resulting table in mySQL should look like this:
id parent_id position left right level title
1 0 0 1 18 0 ROOT
2 1 0 2 17 1 Group
3 2 0 3 8 2 Unit 100
4 2 1 9 12 2 Unit 200
5 3 0 4 5 3 Task 101: This is a task
6 3 1 6 7 3 Task 102: Another task
7 4 0 10 11 3 Task 201: Yet another
8 2 2 13 16 2 Unit 300
9 8 0 14 15 3 Task 301: Another one
The tree would then look like this:
My question is: using PHP, what is the best method to accomplish this? I already have code in place that pulls the data contained in the uploaded CSV file and stores it in an array, but I'm not sure what the logic to convert this to a nested set should look like.
Right now, the data is stored in a 2-dimensional array called $data (in the format $data[$col][$row]):
$data[0][0] = "Code";
$data[0][1] = "100";
$data[0][2] = "200";
$data[0][3] = "101";
$data[0][4] = "102";
$data[0][5] = "201";
$data[0][6] = "300";
$data[0][7] = "301";
$data[1][0] = "Title";
$data[1][1] = "Unit 100";
$data[1][2] = "Unit 200";
$data[1][3] = "Task 101: This is a task";
$data[1][4] = "Task 102: Another task";
$data[1][5] = "Task 201: Yet another";
$data[1][6] = "Unit 300";
$data[1][7] = "Task 301: Another one";
Array ( [0] => Array ( [0] => Code [1] => 100 [2] => 200 [3] => 101 [4] => 102 [5] => 201 [6] => 300 [7] => 301 ) [1] => Array ( [0] => Title [1] => Unit 100 [2] => Unit 200 [3] => Task 101: This is a task [4] => Task 102: Another task [5] => Task 201: Yet another [6] => Unit 300 [7] => Task 301: Another one ) )
Any help would be very much appreciated. I now have the parent_id, position, and level being calculated correctly... I just need to figure out the left/right part. Here is the code I'm currently using (thanks for getting me started Matteo):
$rows = array();
// insert ROOT row
$rows[] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'left' => 1,
'right' => 10000, // just a guess, will need updated later
'level' => 0,
'title' => 'ROOT',
);
echo "<br>";
print_r($rows[0]);
// insert group row
$rows[] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'left' => 2,
'right' => 9999, // just a guess, will need updated later
'level' => 1,
'title' => 'Group',
);
echo "<br>";
print_r($rows[1]);
// next ID to be used
$id = 3;
// keep track of code => ID correspondence
$map = array();
// parse data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// save ID in the map
$map[$data[0][$i]] = $id;
// initialize the current row
$row = array(
'id' => $id,
'parent_id' => 1,
'position' => 0,
'left' => 0,
'right' => 0,
'level' => 1,
'title' => $data[1][$i],
);
// if the code is multiple of 100
if ($data[0][$i] % 100 == 0) {
$row['parent_id'] = 2;
$row['level'] = 2;
$row['position'] = (floor($data[0][$i] / 100)) - 1;
} else {
// get parent id from map
$row['parent_id'] = $map[floor($data[0][$i] / 100) * 100];
$row['level'] = 3;
$row['position'] = $data[0][$i] % 100;
}
// add the row
$rows[] = $row;
++$id;
echo "<br>";
print_r($row);
}
Given your $data array, you could parse it like this:
// this will contain all the rows to be inserted in your DB
$rows = array();
// insert ROOT row
$rows[0] = array(
'id' => 1,
'parent_id' => 0,
'position' => 0,
'level' => 0,
'left' => 1,
'right' => 10000,
'title' => 'ROOT',
);
// insert group row
$rows[1] = array(
'id' => 2,
'parent_id' => 1,
'position' => 0,
'level' => 1,
'left' => 2,
'right' => 9999,
'title' => 'Group',
);
// keep trace of code => ID correspondence
$map = array();
// next ID to be used
$id = 3;
// keep father => sons relationship
$tree = array();
// keep trace of code => row index correspondence
$indexes = array();
// next row index
$index = 2;
// parse your data
for ($i = 1, $c = count($data[0]); $i < $c; ++$i) {
// current code
$code = $data[0][$i];
// save ID in the map
$map[$code] = $id;
// update the indexes map
$indexes[$code] = $index;
// prepare the current row
$row = array(
'id' => $id,
'title' => $data[1][$i],
)
// get the value of code mod 100
$mod = $code % 100;
// if the code is multiple of 100
if ($mod == 0) {
// the parent_id is 2
$row['parent_id'] = 2;
// it is level two
$row['level'] = 2;
// compute position
$row['position'] = floor($code / 100) - 1;
}
else {
// get the parent code
$parent = floor($code / 100) * 100;
// get parent id from map using parent code
$row['parent_id'] = $map[$parent];
// it is level three
$row['level'] = 3;
// save position
$row['position'] = $mod;
// save in relationship tree
$tree[$parent][] = $code;
}
// add the row
$rows[$index] = $row;
// prepare next id
++$id;
// update row index
++$index;
}
// sort the relationship tree base on the parent code (key)
ksort($tree, SORT_NUMERIC);
// next left value
$left = 3;
// now, using the relationship tree, assign left and right
foreach ($tree as $parent => $sons) {
// calculate parent left value
$parentLeft = $left;
// prepare next left value
++$left;
// to be sure that the sons are in order
sort($sons, SORT_NUMERIC);
// assign values to sons
foreach ($sons as $son) {
// index in the rows array
$index = $indexes[$son];
// set left value
$rows[$index]['left'] = $left;
// set right value
$rows[$index]['right'] = $left + 1;
// increment left value
$left += 2;
}
// calculate parent right value
$parentRight = $left;
// prepare next left value
++$left;
// index of parent in the rows array
$index = $indexes[$parent];
// set the values
$rows[$index]['left'] = $parentLeft;
$rows[$index]['right'] = $parentRight;
}
// update group row right value
$rows[1]['right'] = $left;
// prepare next left value
++$left;
// update root row right value
$rows[0]['right'] = $left;
At this point, you can insert all the rows one at a time.
EDIT: now the script should handle all the required values correctly.
I would use Doctrine2 with a Nested Set Extension. You could use a nice and convenient API and don't have to worry about the nested set implementation:
See
http://www.gediminasm.org/article/tree-nestedset-behavior-extension-for-doctrine-2
or http://wildlyinaccurate.com/simple-nested-sets-in-doctrine-2
There are several extensions on github. Actually, i don't know which one is best.
https://github.com/l3pp4rd/DoctrineExtensions
https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior
https://github.com/blt04/doctrine2-nestedset
List item
If your data is flat, you could parse for keywords like 'Unit' or 'Task' to arrange your elements to the needed hierarchical order.
I have banners advertising with number of views, like CPM system.
And for example :
i have 3 banner:
banner1 with 20.000 nr of views
banner2 with 10.000 nr of views
banner3 with 5.000 nr of views
and on my website the banner must to appear in this position (when the page is reloaded) :
banner1 banner2 banner1 banner2 banner3
if the number of views is higher then the probability of apparition is higher
how can i do this in php?
First of all, your system is just... stupid. It perpetuates banners with lots of views while newly created banners with 0 or few views will never get a chance to be picked and thus will never be actually seen...
That being said, if you have an array that looks like this:
$banners = array
(
'banner1' => 1,
'banner2' => 2,
'banner3' => 4,
'banner4' => 8,
'banner5' => 16,
);
You can use a function like this one to weightily pick one banner:
function Probability($data)
{
if (is_array($data) === true) {
$result = 0;
$probability = mt_rand(1, array_sum($data));
foreach ($data as $key => $value) {
$result += $value;
if ($result >= $probability) {
return $key;
}
}
}
return false;
}
Usage (test it # CodePad.org or # IDEOne):
echo Probability($banners); // banner5
Sample from 100 executions:
Array
(
[banner5] => 41
[banner4] => 38
[banner3] => 10
[banner2] => 8
[banner1] => 3
)
Here's a php way to do it
I'm imagining your array will look something like this...
$banners = array(
array (
'name' => 'banner1',
'views' => 20
),
array (
'name' => 'banner2',
'views' => 10
),
array (
'name' => 'banner3',
'views' => 5
)
);
This function basically loops through the banners and however many views a banner has, that many items of its array index are added to an array. Then a random one is chosen. Items with more views have a better chance of being chosen.
function getWeightedRandom( $array ) {
$universe_array = array();
foreach ( $array as $k => $b ) {
$universe += $b['views'];
$universe_array = array_pad( $universe_array, $universe, $k );
}
$rand = mt_rand( 0, count( $universe_array ) -1 );
return $array[ $universe_array[ $rand ] ];
}
$r = getWeightedRandom($banners);
print_r($r);
A simple mysql option is:
select * from banners order by rand() * views desc limit 1
banners with more views will have a higher chance of being the top result