Create n-level menu array from associative array - php

I want to create one n-level menu array from my php array. There can be n-level of children. The main array will be same means not a multidimensional n-level array. I've provided a sample array which meets the array structure I'm using.
$menu_array =
Array
(
[menu-main] => Array
(
[menu_name] => Menu Name 1
[menu_slug] => menu-main
[parent_menu_slug] =>
)
[menu-sub-main] => Array
(
[menu_name] => Sub Menu 1
[menu_slug] => menu-sub-main
[parent_menu_slug] => menu-main
)
[menu-sub-sub-main] => Array
(
[menu_name] => Sub Sub Menu 1
[menu_slug] => menu-sub-sub-main
[parent_menu_slug] => menu-sub-main
)
[menu-main1] => Array
(
[menu_name] => Menu Name 1
[menu_slug] => menu-main1
[parent_menu_slug] =>
)
)
I want to create the n-level array to generate the menu from this array. To do so, I tried below
$x = [];
$tmp = array_combine(array_column($menu_array,'menu_slug'), $menu_array);
//parent menus only
foreach ($tmp as $k => $t) {
if (empty($t['parent_menu_slug'])) {
$x[$t['menu_slug']] = $t;
unset($tmp[$k]);
}
}
$d = [];
$rec_fn = function ($temp,$parent_slug = '') use (&$rec_fn,$x,&$d) {
if ($parent_slug && array_key_exists($parent_slug,$x)) {
$d[$parent_slug]['children'][] = $temp;
} else {
foreach ($temp as $c) {
if (!empty($c['parent_menu_slug'])) {
$d['children'][] = $rec_fn($c,$c['parent_menu_slug']);
}
}
}
return $d;
};
$final_menu = $rec_fn($tmp);
But it is not returning the required result. The required result is
Array
(
[menu-main] => Array
(
[menu_name] => Menu Name 1
[menu_slug] => menu-main
[parent_menu_slug] =>
[children] => array(
[menu-sub-main] => Array
(
[menu_name] => Sub Menu 1
[menu_slug] => menu-sub-main
[parent_menu_slug] => menu-main
[children] => array(
[menu-sub-sub-main] => Array
(
[menu_name] => Sub Sub Menu 1
[menu_slug] => menu-sub-sub-main
[parent_menu_slug] => menu-sub-main
)
.
.
)
)
.
.
)
)
[menu-main1] => Array
(
[menu_name] => Menu Name 1
[menu_slug] => menu-main1
[parent_menu_slug] =>
)
..
)
Any help will be appreciated

From link
I have made some function parameters changes as per requirement and to get exact output I changed branch line.
Here is the recursive function to achieve this,
function buildTree(array $elements, $options = [
'parent_id_column_name' => 'parent_menu_slug',
'children_key_name' => 'children',
'id_column_name' => 'menu_slug'], $parentId = '')
{
$branch = array();
foreach ($elements as $element) {
if ($element[$options['parent_id_column_name']] == $parentId) {
// we will call for every node which is having its child using recursion
$children = buildTree($elements, $options, $element[$options['id_column_name']]);
if ($children) { // if it have children array then save it
$element[$options['children_key_name']] = $children;
}
// we will save every node's parent generated recursive data here
$branch[$element['menu_slug']] = $element; // I changed this line
}
}
// recursive generated data returned
return $branch;
}
Here is working demo.

Related

Merge/Sort Array by Name

I try to sort an array by name. I want to create a structure similar to a menu. First, I allow a function to write values into the array. Then I would like to assign all subpages to the parent (resulting from the structure, such as a URL).
Out
array
(
[0] => array
(
[Pages] => coreViewSites
)
[1] => array
(
[Pages / Create] => create coreView
)
[2] => array
(
[Pages / Duplicate] => coreViewSites
)
[3] => array
(
[Pages / Anarchy] => coreViewSites
)
[4] => array
(
[User] => coreViewUser
)
)
should an array like
array
(
[Pages] => Array
(
[0] => ABC
[Create] => ABC
[Duplicate] => ABC
[Anarchy] => ABC
)
[User] => ABC
)
become.
Do you have an idea how I could solve this?
Assuming the menu items are ordered with parents coming before children, here is how you could do that:
$menu = [];
foreach($input as $path) {
$keys = explode(" / ", key($path)); // Extract the individual menu titles
$last = array_pop($keys); // Pull the last one from it
$loc = &$menu;
foreach($keys as $key) {
// Create menu item if it does not exist yet
if (!isset($loc[$key])) $loc[$key] = [];
// When a menu gets sub-menu, move the title in index 0 of a new array
if (!is_array($loc[$key])) $loc[$key] = [$loc[$key]];
$loc = &$loc[$key]; // Set the pointer to that submenu
}
$loc[$last] = reset($path); // At the deepest level assign the menu title.
}
// Print result:
var_export($menu);
Output:
array (
'Pages' => array (
0 => 'coreViewSites',
'Create' => 'create coreView',
'Duplicate' => 'coreViewSites',
'Anarchy' => 'coreViewSites',
),
'User' => 'coreViewUser',
)

Recursive Multidimensional Array to HTML nested code

Im trying to create nested blocks of HTML code from a Multidimensional Array in PHP using a recursive function. However I can not seem to print the tags in a nested order.
Here is the original array:
$array_1 =
array
(
array
(
'id' => '1',
'tag' => 'div1',
'parent' => '0'
),
array
(
'id' => '2',
'tag' => 'div2',
'parent' => '1'
),
array
(
'id' => '3',
'tag' => 'div3',
'parent' => '2'
),
array
(
'id' => '4',
'tag' => 'div4',
'parent' => '2'
),
array
(
'id' => '5',
'tag' => 'div5',
'parent' => '0'
),
array
(
'id' => '6',
'tag' => 'div6',
'parent' => '5'
),
array
(
'id' => '7',
'tag' => 'div7',
'parent' => '0'
)
);
The first thing I do is to use a function to turn this array into a multidimensional array by building a tree structure using the parent element of each record as reference:
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element)
{
if ($element['parent'] == $parentId)
{
$children = buildTree($elements, $element['id']);
if ($children)
{
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
$tree_1 = buildTree($array_1);
Once this is done the multidimensional array should look like this:
Array
(
[0] => Array
(
[id] => 1
[name] => div1
[parent] => 0
[children] => Array
(
[0] => Array
(
[id] => 2
[name] => div2
[parent] => 1
[children] => Array
(
[0] => Array
(
[id] => 3
[name] => div3
[parent] => 2
)
[1] => Array
(
[id] => 4
[name] => div4
[parent] => 2
)
)
)
)
)
[1] => Array
(
[id] => 5
[name] => div5
[parent] => 0
[children] => Array
(
[0] => Array
(
[id] => 6
[name] => div6
[parent] => 5
)
)
)
[2] => Array
(
[id] => 7
[name] => div7
[parent] => 0
)
)
The next thing to do is to output the html elements in a nested order. In this example Im using symbolic tag names such as div1, div2, etc; but in reality they could be any html tags like divs, h1, ul,li, form, etc; with their opening and closing tags.
To accomplish this I use the following recursive function, however my problem is that it does not show the html elements in a nested order as they should be.
function recursive($array, $level = 0)
{
foreach($array as $key => $value)
{
if (!is_array($value) && $key == "tag") { echo str_repeat(" ", $level), "[".$value."]", ''; }
//If $value is an array.
if(is_array($value))
{
//We need to loop through it.
recursive($value, $level + 1);
}
else
{
//It is not an array, so print it out.
if ($key == "tag") { echo "[/".$value."]", '<br>'; }
}
}
}
$tree_2 = recursive($tree_1);
echo $tree_2;
This is how it currently outputs the html tags from the array:
[div1][/div1]
[div2][/div2]
[div3][/div3]
[div4][/div4]
[div5][/div5]
[div6][/div6]
[div7][/div7]
And this is how the html tags should be output in reality based on the multidimensional array's nested order (please note how some div tags contain other div tags before their closing element):
[div1]
[div2]
[div3][/div3]
[div4][/div4]
[/div2]
[/div1]
[div5]
[div6][/div6]
[/div5]
[div7][/div7]
How can I get the html tags printed in the right nested order like in the last example? What am I missing? Thanks so much!
The issue is that the key tag is before children in the $tree_1 array.
Whilst I would personally change your data structure around to make it "easier" to use in the recursive function, you can change your recursive function to make it work like this:
function recursive($array, $level = 0)
{
// Check if we are using an associative array or indexed array
if(isset($array['tag'])) {
echo str_repeat(" ", $level), "[" . $array['tag'] . "]", '';
if(isset($array['children'])) {
echo "<br />";
recursive($array['children'], $level + 1);
echo str_repeat(" ", $level), "[/" . $array['tag'] . "]" , '';
} else {
echo "[/" . $array['tag'] . "]";
}
echo "<br />";
} else {
foreach($array as $key => $value)
{
recursive($value, $level + 1);
}
}
}
Add additional line breaks and formatting where required
A rewrite of your function:
function recursive2($array, &$level, $indentClosingTag = true)
{
$level++;
for($i = 0; $i<count($array); $i++) {
$data = $array[$i];
$indent = str_repeat(' ', $level);
echo $indent . "[".$data['tag'].']';
if (isset($data['children'])) {
echo '<br>';
$level++;
recursive2($data['children'], $level, true);
$level--;
} else {
$indentClosingTag = false;
}
echo ($indentClosingTag ? $indent : ''). "[/".$data['tag'].']'.'<br>';
}
$level--;
}
$l = 0;
recursive2($tree_1, $l);

Sort array values based on parent/child relationship

I am trying to sort an array to ensure that the parent of any item always exists before it in the array. For example:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[2] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[3] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[4] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[5] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
In the above array this "fails" as [1][2] (Sam) does not yet exist and nor does [4][2] (Tom).
The correct output would be as, in this case, as both Sam and Tom's parents already exist before they appear in the array:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[2] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[3] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[4] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[5] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
I found an answer https://stackoverflow.com/a/12961400/1278201 which was very close but it only seems to go one level deep (i.e. there is only ever one parent) whereas in my case there could be 1 or 10 levels deep in the hierarchy.
How do I sort the array so no value can appear unless its parent already exists before it?
This will trivially order the array (in O(n)) putting first all those with no parent, then these whose parent is already in the array, iteratively, until there's no children having the current element as parent.
# map the children by parent
$parents = ['' => []];
foreach ($array as $val) {
$parents[$val[2]][] = $val;
}
# start with those with no parent
$sorted = $parents[''];
# add the children the current nodes are parent of until the array is empty
foreach ($sorted as &$val) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
This code requires PHP 7, it may not work in some cases under PHP 5. - for PHP 5 compatibility you will have to swap the foreach ($sorted as &$val) with for ($val = reset($sorted); $val; $val = next($sorted)):
# a bit slower loop which works in all versions
for ($val = reset($sorted); $val; $val = next($sorted)) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
Live demo: https://3v4l.org/Uk6Gs
I have two different version for you.
a) Using a "walk the tree" approach with recursion and references to minimize memory consumption
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
$list = [];
generateList($data, '', $list);
var_dump($list);
function generateList($data, $id, &$list) {
foreach($data as $d) {
if($d[2] == $id) {
$list[] = $d; // Child found, add it to list
generateList($data, $d[0], $list); // Now search for childs of this child
}
}
}
b) Using phps built in uusort()function (seems only to work up to php 5.x and not with php7+)
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
usort($data, 'cmp');
var_dump($data);
function cmp($a, $b) {
if($a[2] == '' || $a[0] == $b[2]) return -1; //$a is root element or $b is child of $a
if($b[2] == '' || $b[0] == $a[2]) return 1; //$b is root element or $a is child of $b
return 0; // both elements have no direct relation
}
I checked this works in PHP 5.6 and PHP 7
Sample array:
$array = Array(0 => Array(
0 => 207306,
1 => 'Bob',
2 => '',
),
1 => Array
(
0 => 199730,
1 => 'Sam',
2 => 199714,
),
2 => Array
(
0 => 199728,
1 => 'Simon',
2 => 207306,
),
3 => Array
(
0 => 199714,
1 => 'John',
2 => 207306,
),
4 => Array
(
0 => 199716,
1 => 'Tom',
2 => 199718,
),
5 => Array
(
0 => 199718,
1 => 'Phillip',
2 => 207306,
),
6 => Array
(
0 => 199720,
1 => 'James',
2 => 207306,
),
);
echo "<pre>";
$emp = array();
//form the array with parent and child
foreach ($array as $val) {
$manager = ($val[2] == '') ? 0 : $val[2];
$exist = array_search_key($val[2], $emp);
if ($exist)
$emp[$exist[0]][$val[0]] = $val;
else
//print_R(array_search_key(199714,$emp));
$emp[$manager][$val[0]] = $val;
}
$u_emp = $emp[0];
unset($emp[0]);
//associate the correct child/emp after the manager
foreach ($emp as $k => $val) {
$exist = array_search_key($k, $u_emp);
$pos = array_search($k, array_keys($u_emp));
$u_emp = array_slice($u_emp, 0, $pos+1, true) +
$val +
array_slice($u_emp, $pos-1, count($u_emp) - 1, true);
}
print_R($u_emp); //print the final result
// key search function from the array
function array_search_key($needle_key, $array, $parent = array())
{
foreach ($array AS $key => $value) {
$parent = array();
if ($key == $needle_key)
return $parent;
if (is_array($value)) {
array_push($parent, $key);
if (($result = array_search_key($needle_key, $value, $parent)) !== false)
return $parent;
}
}
return false;
}
Find the below code that might be helpful.So, your output is stored in $sortedarray.
$a=array(array(207306,'Bob',''),
array (199730,'Sam',199714),
array(199728,'Simon',207306),
array(199714,'John',207306),
array(199716,'Tom',199718),
array(199718,'Phillip',207306),
array(199720,'James',207306));
$sortedarray=$a;
foreach($a as $key=>$value){
$checkvalue=$value[2];
$checkkey=$key;
foreach($a as $key2=>$value2){
if($key<$key2){
if ($value2[0]===$checkvalue){
$sortedarray[$key]=$value2;
$sortedarray[$key2]=$value;
}else{
}
}
}
}
print_r($sortedarray);
What about this approach:
Create an empty array result.
Loop over your array and only take the items out of it where [2] is empty and insert them into result.
When this Loop is done you use a foreach-Loop inside a while-loop. With the foreach-Loop you take every item out of your array where [2] is already part of result. And you do this as long as your array contains anything.
$result = array();
$result[''] = 'root';
while(!empty($yourArray)){
foreach($yourArray as $i=>$value){
if(isset($result[$value[2]])){
// use the next line only to show old order
$value['oldIndex'] = $i;
$result[$value[0]] = $value;
unset($yourArray[$i]);
}
}
}
unset($result['']);
PS: You may run into trouble by removing parts of an array while walking over it. If you do so ... try to solve this :)
PPS: Think about a break condition if your array have an unsolved loop or a child without an parent.
you can use your array in variable $arr and use this code it will give you required output.
function check($a, $b) {
return ($a[0] == $b[2]) ? -1 : 1;
}
uasort($arr, 'check');
echo '<pre>';
print_r(array_values($arr));
echo '</pre>';

From php multidimensional array trying to create nested ul li menu (unlimited nested levels)

Here is what i have got http://codepad.org/iDoXXsLX
Have array like this
Array
(
[0] => Array
(
[NumberRenamed] => 17
[TopicName] => Products
[UpperLevelNumberRenamed] => 0
)
[17] => Array
(
[0] => Array
(
[1] => Array
(
[NumberRenamed] => 18
[TopicName] => Computers
[UpperLevelNumberRenamed] => 17
)
)
)
[18] => Array
(
[0] => Array
(
[2] => Array
(
[NumberRenamed] => 16
[TopicName] => Laptops
[UpperLevelNumberRenamed] => 18
)
)
)
[16] => Array
(
[0] => Array
(
[4] => Array
(
[NumberRenamed] => 8
[TopicName] => Dell
[UpperLevelNumberRenamed] => 16
)
)
)
)
Top level item is Products, first sub-level item is Computers, next sub-level is Laptops, then again next sub-level Dell
For each sub-level item UpperLevelNumberRenamed == to closest upper level NumberRenamed.
Want to get result like this
Products
Computers
Laptops
Dell
Acer
Desktops
Home
Tried this
foreach( $main_topics as $k_main_topics => $v_main_topics ){
if( isset($v_main_topics['UpperLevelNumberRenamed']) and $v_main_topics['UpperLevelNumberRenamed'] == 0 ){
//print only top level topics
echo $v_main_topics['TopicName']. '<br/>';
}
else{//if not top level topic
foreach( $v_main_topics[0] as $k_v_main_topics_0 => $v_v_main_topics_0 ){
echo $v_v_main_topics_0['TopicName']. '<br/>';
}//foreach( $v_main_topics[0] as $k_v_main_topics_0 => $v_v_main_topics_0 )
}//else{
}//foreach( $main_topics as $k_main_topics => $v_main_topics )
But get this
Products
Home
Computers
Laptops
Desktops
Dell
Acer
Something incorrect, but can not understand what. Please, advice what need to correct/change in the code
Trying another way
Initial array is one dimensional array. Trying to get ul li navigation from one dimensional.
Here is what i did http://codepad.org/OLtxyL4X
Here's a summary of what it does:
flatten the array recursively
build a multi-dimensional relation map
create 1D relationships that link UpperLevelNumberRenamed to NumberRenamed
print out the multi-dimensional as an ul-li list.
Here it is:
$flat = array();
foreach (new RecursiveIteratorIterator(new RecursiveArrayIterator($main_topics)) as $i)
$flat[] = $i;
$final = array();
$defs = array();
for ($i = 0; $i < count($flat); $i += 3)
if ($flat[$i + 2] == 0) {
$final[$flat[$i + 1]] = array();
$defs[$flat[$i]] = &$final[$flat[$i + 1]];
} else {
$defs[$flat[$i + 2]][$flat[$i + 1]] = array();
$defs[$flat[$i]] = &$defs[$flat[$i + 2]][$flat[$i + 1]];
}
function array2ul($array) {
$out = "<ul>";
foreach($array as $key => $elem)
$out = is_array($elem) ?
$out . "<li><span>$key</span>" . array2ul($elem) . "</li>" :
$out = $out."<li><span>$key:[$elem]</span></li>";
$out = $out . "</ul>";
return $out;
}
echo array2ul($final);
Output:
<ul><li><span>Products</span><ul><li><span>Computers</span><ul><li><span>Laptops</span><ul><li><span>Dell</span><ul></ul></li><li><span>Acer</span><ul></ul></li></ul></li><li><span>Desktops</span><ul></ul></li></ul></li></ul></li><li><span>Home</span><ul></ul></li></ul>
This shall be a working example using recursion, not tested though:
Define the array
$main_array = Array
(
'10' => Array
(
'name' => 'Products'
'children' => Array
(
'12' => Array
(
'name' => 'Laptop',
'children' => Array
(
'13' => Array
(
'name' => 'Dell',
),
'14' => Array
(
'name' => 'Acer',
)
)
)
'14' => Array
(
'name' => 'Desktop',
'children' => Array
(
'15' => Array
(
'name' => 'Sony',
),
'16' => Array
(
'name' => 'Apple',
)
)
),
)
)
)
Create and call the function :
function createList($main_topics)
{
if($main_topics == null || sizeof($main_topics) <= 0)
{
return '';
}
$list = '<ul>';
foreach($main_topics as $k_main_topics => $v_main_topics )
{
$list .= '<li id="' . $k_main_topics'"> '. $v_main_topics['name'] . ' ' . createList(isset($v_main_topics["children"]) ? $v_main_topics["children"] : null) . '</li>' ;
}
$list .= '</ul>';
return $list;
}
echo createList($main_array);

PHP: Iterating through array?

I want a function that
searches through my array, and
returns all the
children to a specific node. What is
the most appropriate way to do this?
Will recursion be necessary in this case?
I have previously constructed a few quite complex functions that iterates with or without the help of recursion through multi-dimensional arrays and re-arranging them, but this problem makes me completely stuck and I can't just get my head around it...
Here's my array:
Array
(
[1] => Array (
[id] => 1
[parent] => 0
)
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
UPDATE:
The output which I want to get. Sorry for the bad example, but I'll blame it on lack of knowledge on how to format the stuff I need to do :)
function getAllChildren($id) {
// Psuedocode
return $array;
}
getAllChildren(1); // Outputs the following:
Array
(
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
$nodes = array( 1 => array ( 'id' => 1,
'parent' => 0
),
2 => array ( 'id' => 2,
'parent' => 1
),
3 => array ( 'id' => 3,
'parent' => 2
)
);
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if ($item['parent'] == $needle) {
$nodes[$key] = $item;
$nodes = $nodes + searchItem($item['id'],$haystack);
}
}
return $nodes;
}
$result = searchItem('1',$nodes);
echo '<pre>';
var_dump($result);
echo '</pre>';
Non-recursive version of the searchItem() function:
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if (($item['parent'] == $needle) || array_key_exists($item['parent'],$nodes)) {
$nodes[$key] = $item;
}
}
return $nodes;
}
(assumes ordering of the parents/children, so a child node isn't included in the array unless the parent is already there)
<?php
function searchItem($needle)
{
foreach ($data as $key => $item)
{
if ($item['id'] == $needle)
{
return $key;
}
}
return null;
}
?>
Check out the array_walk_recursive() function in PHP:
http://www.php.net/manual/en/function.array-walk-recursive.php

Categories