List child parents from multidimensional array - php

I've been on this for two days now and I'm completely stuck. Google and SO couldn't help me further and I'm overlooking something....
This is what I want
Home
Typography
404 Page not found
The company
The company/Team
The company/Team/Rick
The company/Team/Rick/Opleiding
The company/Team//Rob
The company/Office
The company/Office/Contact
The company/Office/Route
This is my array
Array
(
[0] => Array
(
[id] => 55
[parent_id] => 0
[title] => Home
[sub] => Array
(
)
)
[1] => Array
(
[id] => 27
[parent_id] => 0
[title] => Typography
[sub] => Array
(
)
)
[2] => Array
(
[id] => 56
[parent_id] => 0
[title] => 404 Page not found
[sub] => Array
(
)
)
[3] => Array
(
[id] => 68
[parent_id] => 0
[title] => The company
[sub] => Array
(
[0] => Array
(
[id] => 73
[parent_id] => 68
[title] => Team
[sub] => Array
(
[0] => Array
(
[id] => 74
[parent_id] => 73
[title] => Rick
[sub] => Array
(
[0] => Array
(
[id] => 79
[parent_id] => 74
[title] => Opleiding
[sub] => Array
(
)
)
)
)
[1] => Array
(
[id] => 75
[parent_id] => 73
[title] => Rob
[sub] => Array
(
)
)
)
)
[1] => Array
(
[id] => 76
[parent_id] => 68
[title] => Office
[sub] => Array
(
[0] => Array
(
[id] => 78
[parent_id] => 76
[title] => Contact
[sub] => Array
(
)
)
[1] => Array
(
[id] => 77
[parent_id] => 76
[title] => Route
[sub] => Array
(
)
)
)
)
)
)
)
This is what I have so far
public function BuildTitle($array, &$parents = null)
{
$html = '';
foreach($array as $page)
{
if($page['parent_id'] != 0)
{
$html .= $parents.'/'.$page['title'].'<br/>';
$parents = $parents.'/'.$page['title'];
}
else
{
$html .= $page['title'].'<br/>';
$parents = $page['title'];
}
$html .= $this->BuildTitle($page['sub'], $parents);
}
return $html;
}
And that returns
Home
Typography
404 Page not found
The company
The company/Team
The company/Team/Rick
The company/Team/Rick/Opleiding
The company/Team/Rick/Opleiding/Rob
The company/Team/Rick/Opleiding/Rob/Office
The company/Team/Rick/Opleiding/Rob/Office/Contact
The company/Team/Rick/Opleiding/Rob/Office/Contact/Route
Parent/child relations are unlimited. What am I doing wrong/missing? Thanks in advance!

You're modifying the same $parents value in every iteration. So, as long as the parent isn't 0, it just appends the current child to whatever the path is.
Imagine for instance you had the following scenario:
foo
bar
baz
When processing foo, it'd start with $parents = 'foo'.
It then goes through each of the children, updating $parents:
bar makes $parents = $parents . '/' . $page['title'] = 'foo' . '/' . 'bar' = 'foo/bar'.
baz makes $parents = $parents . '/' . $page['title'] = 'foo/bar' . '/' . 'bar' = 'foo/bar/baz'.
What you want is this:
public function BuildTitle($array, $parents = null)
{
$html = '';
foreach($array as $page)
{
if($page['parent_id'] != 0)
{
$html .= $parents.'/'.$page['title'].'<br/>';
$new_parents = $parents.'/'.$page['title'];
}
else
{
$html .= $page['title'].'<br/>';
$new_parents = $page['title'];
}
$html .= $this->BuildTitle($page['sub'], $new_parents);
}
return $html;
}

Related

PHP break Nested Multi-dimensional array into single multi-dimensional array

I want to break the below nested array in simple associative array.
Input
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
[children] => Array
(
[0] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
[children] => Array
(
)
)
[1] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
[children] => Array
(
[0] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
[children] => Array
(
)
)
[1] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
Output
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
)
[1] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
)
[2] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
)
[3] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
)
[4] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
)
)
Need help in making this type of array from the given array. I tried many things with for loops, but get stuck when in case there are many nested array and that solution does not fit correctly to my requirement.
There is one root node and others are child nodes and many parent nodes can have child nodes.
There are a ton of ways to do this, here are a couple of simple examples. If you don;t care about maintaining order, the recursive function is pretty simple. If you do need to maintain the order of the elements as they are encountered while traversing the tree (to render them as tables for example), it's just a bit more of a faff.
<?php
function flattenTree($array)
{
$output = [];
foreach($array as $currBranch)
{
if(!empty($currBranch['children']))
{
$children = flattenTree($currBranch['children']);
$output = array_merge($output, $children);
}
unset($currBranch['children']);
$output[] = $currBranch;
}
return $output;
}
function flattenTreeMaintainingOrder($array)
{
$output = [];
foreach($array as $currBranch)
{
$children = (array_key_exists('children', $currBranch)) ? $currBranch['children']:[];
unset($currBranch['children']);
$output[] = $currBranch;
if(!empty($children))
{
$children = flattenTreeMaintainingOrder($children);
$output = array_merge($output, $children);
}
}
return $output;
}
$flat = flattenTree($array);
$flatOrdered = flattenTreeMaintainingOrder($array);
print_r($flat) . PHP_EOL;
print_r($flatOrdered) . PHP_EOL;
A recursive function is one option...
function extractChildren($parent, $farr) {
$children = $parent['children'];
if (!$children || count($children)==0) return $farr;
unset($parent['children']);
$farr[]= $parent;
return extractChildren($children, $farr);
}
$finalarray=array();
// $array is the array you have in your question
foreach ($array as $parent) {
$finalarray = extractChildren($parent, $finalarray);
}
As #El_Vanya mentioned above, there are scads of other ways to accomplish this here: How to Flatten a Multidimensional Array?

How can I create a function to display an array as a tree?

How can I create a function to display an array as a tree. For example I want to obtain a decision tree which I want to walk until I get to the leafs based on the branch's values. I create the tree like bellow:
$tree= new DS_Tree();
$node=array('name' => 'start');
$tree->insert_node($node);
$tree->goto_root();
$mytree = new id3();
$mytree->init($data_array_AttrList,$data_array_values,$data_class,$data_array_instances,$tree);
$mytree->run();
echo '<pre class="brush: php">';
print_r($mytree->tree->draw_tree());
echo '</pre>';
The function draw_tree() is:
public function draw_tree() {
return $this->nodes;
}
The function that creates my tree is:
private function make_tree($attr) {
foreach($this->Values[$attr] as $v) {
$subset = $this->get_subset($attr, $v);
if($the_class = $this->has_same_class($subset)) {
$node =array(
'name' => $attr,
'arc' => $v
);
$this->tree->insert_node($node);
$this->Instance = array_diff_key($this->Instance, $subset);
} else {
$node =array(
'name' => $this->Classa,
'arc' => $v
);
$unresolved = $this->tree->insert_node($node);
}
}
if (isset($unresolved)) {
$this->tree->goto_index($unresolved);
}
}
}
The result is:
Array
(
[0] => Array
(
[name] => Time
[parent] =>
[children] => Array
(
[0] => 1
)
)
[1] => Array
(
[name] => Focus
[arc] => Array
(
[0] => 2 day/week
[1] => 3 day/week
[2] => 4 day/week
[3] => 5 day/week
[4] => 6 day/week
)
[parent] => 0
[children] => Array
(
[0] => 2
)
)
[2] => Array
(
[name] => Dificulty
[arc] => Array
(
[0] => Weght loss
[1] => Mantain weight
[2] => Gain Mass
)
[parent] => 1
[children] => Array
(
[0] => 3
)
)
[3] => Array
(
[name] => Sex
[arc] => Array
(
[0] => Beginner
[1] => Intermediar
[2] => Advance
)
[parent] => 2
[children] => Array
(
[0] => 4
)
)
[4] => Array
(
[name] => Array
(
[Exercise] => Array
(
[0] => Array
(
[0] => Ex1
[1] => Ex2
[2] => Ex3
[3] => Ex4
[4] => Ex5
)
)
)
[arc] => Array
(
[0] => F
[1] => M
)
[parent] => 3
)
)
Just to display an array as a tree:
echo "<pre>";
var_dump($array);
echo "</pre>";
Here is a way to iterate through this data structure and look for a certain value:
function recurseFind($tree, $find, $path = "") {
if (!is_array($tree)) {
// it is a leaf:
if ($tree === $find) {
return $path; // return path where we found it
}
return false;
}
foreach($tree as $key => $value) {
$result = recurseFind($value, $find, $path . (is_numeric($key) ? "[$key]" : "['$key']"));
if ($result !== false) return $result;
}
return false;
}
For the sample input you provided, if you would call it like this:
echo recurseFind($tree, "Mantain weight", '$tree');
Outputs the "location" of that value (first match only) in a format that can be evaluated in PHP:
$tree[2]['arc'][1]

Copying a multi dimensional array of nodes into another array

I am looking to convert a multi dimensional array into another multidimensional array using a recursive function.
Source array :
Array
(
[1] => Array
(
[id] => 1
[source_name] => kk56ca1d0f2378f
[company_id] => 1
[lft] => 1
[rgt] => 18
[parent_id] => 0
[children] => Array
(
[2] => Array
(
[id] => 2
[source_name] => kk56ca1d17f3f63
[company_id] => 1
[lft] => 2
[rgt] => 3
[parent_id] => 1
[children] => Array
(
)
)
[3] => Array
(
[id] => 3
[source_name] => kk56ca1d1ebe975
[company_id] => 1
[lft] => 4
[rgt] => 13
[parent_id] => 1
[children] => Array
(
[6] => Array
(
[id] => 6
[source_name] => kk56ca1fc882ac0
[company_id] => 1
[lft] => 5
[rgt] => 10
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
which I need to get into the format of
Array
(
[0] => Array
(
[id] => 1
[text] => kk56ca1d0f2378f
[parent_id] => 0
[nodes] => Array
(
[0] => Array
(
[id] => 2
[text] => kk56ca1d17f3f63
[parent_id] => 1
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 3
[text] => kk56ca1d1ebe975
[parent_id] => 1
[nodes] => Array
(
[0] => Array
(
[id] => 6
[text] => kk56ca1fc882ac0
[parent_id] => 3
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 15
[text] => kk
[parent_id] => 3
[nodes] => Array
(
)
)
)
)
)
)
)
I have been trying for hours and getting nowhere with this. Any help would be really appreciated.
The source array has associative indexes (though they are numbers) and the destination array has numerical indexes. Besides this, just need to remove a few indexes and change names of a few.
EDIT :
Specific changes :
change index name source_name to text
change index name children to nodes
unset indexes lft, rgt, company_id
I do not have much experience with recursion so I have trying fruitlessly.
This is what I could come up with :
// pass array of nodes
function convert_array($from){
// this is a node
if(isset($from['source_name']))
{
$temp = array();
$temp['id'] = $from['id'];
convert_array($from['children']);
}
// this is an array of nodes
else
{
foreach($from as $arr)
{
$ret = convert_array($arr);
print_r($ret);
}
}
}
But I am not able to understand what data to be returned and how the new array builds up from the return values.
Here the working function:
function convert_array( $array )
{
$retval = array();
foreach( $array as $row )
{
$child = array();
$child['id'] = $row['id'];
$child['text'] = $row['source_name'];
$child['parent_id'] = $row['parent_id'];
if( count( $row['children'] ) )
{ $child['nodes'] = convert_array( $row['children'] ); }
else
{ $child['nodes'] = array(); }
$retval[] = $child;
}
return $retval;
}
3v4l demo
I think it is self-explanatory, BTW: we init an empty array ($retval), then we perform a foreach loop through all array argument: for each element, we init a new array and we add it id, source_name as text and parent_id; if the children index has elements, we perform a recursive call to fill nodes array index, otherwise we set it to empty array.

go array from bottom to up level

First of all, sorry for my english.
I want to go through an array of any levels, but I want to go in the bottom level to up level and update a value of key recursively, but an example is better than a text:
this is my example code:
Array
(
[1] => Array
(
[ItemText] => Home
[ItemLink] => index.php
[count] => 0
[id] => 1
[ParentID] =>
[Children] => Array
(
[2] => Array
(
[ItemText] => Home Sub 1
[ItemLink] => somepage.php
[id] => 2
[count] => 0
[ParentID] => 1
[Children] => Array
(
[3] => Array
(
[ItemText] => Home Sub 2
[ItemLink] => somepage2.php
[id] => 3
[count] => 1
[ParentID] => 2
[Children] => Array
(
)
)
[4] => Array
(
[ItemText] => Contact
[ItemLink] => contact.php
[id] => 4
[count] => 1
[ParentID] => 2
[Children] => Array
(
)
)
)
)
)
)
)
note the count key in any level of array. Each level is a child of the "current" position. I need this:
Array
(
[1] => Array
(
[ItemText] => Home
[ItemLink] => index.php
[count] => **2**
[id] => 1
[ParentID] =>
[Children] => Array
(
[2] => Array
(
[ItemText] => Home Sub 1
[ItemLink] => somepage.php
[id] => 2
[count] => **2**
[ParentID] => 1
[Children] => Array
(
[3] => Array
(
[ItemText] => Home Sub 2
[ItemLink] => somepage2.php
[id] => 3
[count] => 1
[ParentID] => 2
[Children] => Array
(
)
)
[4] => Array
(
[ItemText] => Contact
[ItemLink] => contact.php
[id] => 4
[count] => 1
[ParentID] => 2
[Children] => Array
(
)
)
)
)
)
)
)
I want to accumulate and sum the count of all child in the current position and go through the prev level and accumulate again all count of the current level and that's to the up level.
I appreciate all your help. Thanks in advance.
EDIT
I adapted the function of #HamzaKubba to my needs and this works for me. I put this for those to require:
function explore(& $node) {
$count = 0;
if (count($node) > 0) {
foreach ($node as &$value) {
if (!isset($value['count']))
$value['count'] = 0;
if (count($value['Children']) > 0)
$value['count'] += explore($value['Children']);
$count += $value['count'];
}
}
return $count;
}
pseudoCode of general template:
function explore(node) {
foreach (child of node) {
explore(child)
}
// now that we're done with children, do logic/calculation here
doSomething(node)
}
pseudoCode of what you want (from what I understand):
function explore(node) {
foreach (child of node) {
node.count = node.count + explore(child)
}
return node.count
}
to correct your new code:
function explore_($node) {
if (!isset($node['count']))
$node['count'] = 0;
foreach ($node['Children'] as $child) {
$node['count'] = $node['count'] + explore_($child);
}
return $node['count'];
}

PHP Reorder array to reflect parent / id hierarchy

How do I rearrange the following array
[0] => Array
(
[id] => 1
[parent_id] => 0
[name] => Accueil
)
[1] => Array
(
[id] => 2
[parent_id] => 0
[name] => Exposants
)
[2] => Array
(
[id] => 3
[parent_id] => 0
[name] => Visiteurs
)
[3] => Array
(
[id] => 4
[parent_id] => 0
[name] => Medias
)
[4] => Array
(
[id] => 5
[parent_id] => 0
[name] => Activités
)
[5] => Array
(
[id] => 6
[parent_id] => 1
[name] => Contact
)
[6] => Array
(
[id] => 7
[parent_id] => 3
[name] => Partenaires
)
[7] => Array
(
[id] => 8
[parent_id] => 2
[name] => News
)
So I come up with an array that reflects the hierarchy as shown by the id and parent_id fields? The array key is the ID field of array elements are parents. Inside this array is each time a child array that has its ID field as the key. Sample:
[1] => Array
(
[name] => Accueil
[children] => array(
[0] => bla,
[3] => bla2
)
)
[2] => Array
(
[name] => Something
[children] => array(
[4] => bla3,
)
)
Works for any depth and allows children to precede parents:
<?php
$p = array(0 => array());
foreach($nodes as $n)
{
$pid = $n['parent_id'];
$id = $n['id'];
if (!isset($p[$pid]))
$p[$pid] = array('child' => array());
if (isset($p[$id]))
$child = &$p[$id]['child'];
else
$child = array();
$p[$id] = $n;
$p[$id]['child'] = &$child;
unset($p[$id]['parent_id']);
unset($child);
$p[$pid]['child'][] = &$p[$id];
}
$nodes = $p['0']['child'];
unset($p);
?>
Use var_dump on the $nodes result to see the structure. It is close to what you suggested. The major difference is that the keys are not the ids.
You could make this more DRY, but it's a quick and dirty way to handle it. Also, you could remove 6 lines if you could guarantee that each child record has a valid parent record and that the valid parent record precedes the child record in the original array.
$sorted = array();
foreach( $orig_ary as $item ) {
if ( $item['parent_id'] === 0 ) {
if ( !array_key_exists( $item['id'], $sorted ) ) {
$sorted[ $item['id'] ] = array(
'name' => '',
'children' => array()
);
}
$sorted[ $item['id'] ]['name'] = $item['name'];
} else {
if ( !array_key_exists( $item['parent_id'], $sorted ) ) {
$sorted[ $item['parent_id'] ] = array(
'name' => '',
'children' => array()
);
}
$sorted[ $item['parent_id'] ]['children'][ $item['id'] ] = $item['name'];
}
}

Categories