7

I have an array with tree data (by parent id). I want to convert it to multidimensional array. What is the best way to achieve that? Is there any short function for that?

Source array:

$source = array(
    '0' => array(
            'Menu' => array(
                    'id' => 45
                    'name' => 'Home'
                    'parent_id' => 1
            )
    )
    '1' => array(
            'Menu' => array(
                    'id' => 47
                    'name' => 'Get started'
                    'parent_id' => 1
            )
    )
    '2' => array(
            'Menu' => array(
                    'id' => 72
                    'name' => 'Attributes'
                    'parent_id' => 71
            )
    )
    '3' => array(
            'Menu' => array(
                    'id' => 73
                    'name' => 'Headings'
                    'parent_id' => 71
            )
    )
    '4' => array(
            'Menu' => array(
                    'id' => 75
                    'name' => 'Links'
                    'parent_id' => 71
            )
    )
    '5' => array(
            'Menu' => array(
                    'id' => 59
                    'name' => 'Images'
                    'parent_id' => 75
            )
    )
    '6' => array(
            'Menu' => array(
                    'id' => 65
                    'name' => 'Lists'
                    'parent_id' => 75
            )
    )
);

Some parents are missing from the source array. I would like the items with missing parent to be root. Result array:

$result = array(
    '0' => array(
            'Menu' => array(
                    'id' => 45
                    'name' => 'Home'
                    'parent_id' => 1
            )
            'Children' => array()
    )
    '1' => array(
            'Menu' => array(
                    'id' => 47
                    'name' => 'Get started'
                    'parent_id' => 1
            )
            'Children' => array()
    )
    '2' => array(
            'Menu' => array(
                    'id' => 72
                    'name' => 'Attributes'
                    'parent_id' => 71
            )
            'Children' => array()
    )
    '3' => array(
            'Menu' => array(
                    'id' => 73
                    'name' => 'Headings'
                    'parent_id' => 71
            )
            'Children' => array()
    )
    '4' => array(
            'Menu' => array(
                    'id' => 75
                    'name' => 'Links'
                    'parent_id' => 71
            )
            'Children' => array(
                    '0' => array(
                        'Menu' => array(
                            'id' => 59
                            'name' => 'Images'
                            'parent_id' => 75
                        )
                        'Children' => array()
                    )
                    '1' => array(
                        'Menu' => array(
                            'id' => 65
                            'name' => 'Lists'
                            'parent_id' => 75
                        )
                        'Children' => array()
                   )
            )
     )
);

Update: removed square brackets.

Cœur
  • 37,241
  • 25
  • 195
  • 267
bancer
  • 7,475
  • 7
  • 39
  • 58
  • 1
    That first one is already a multidimensional array. Multidimensional simply means arrays inside arrays. – animuson Jul 16 '10 at 01:09
  • uh this isn't valid PHP .. what does [0] => array(... or ['Menu'] => array(... mean? So I'm rather stuck not understanding the shape of your input data. – Scott Evernden Jul 16 '10 at 01:14
  • @animuson: yeah. you are right. i was not precise. but i think it is clear what i want, isn't it? @SpawnCxy: yes @Scott: that's a valid PHP. array keys could be strings, not only numbers – bancer Jul 16 '10 at 01:19
  • You are aware that in CakePHP you can structure your model as a proper MPTT tree, attach the Tree behavior and simply do a `$model->find('threaded')` to get this result? http://book.cakephp.org/view/1339/Tree – deceze Jul 16 '10 at 02:05
  • @deceze: Yes, I know about 'threaded'. I thought earlier about that. The source array in my question is the result of some manipulations with query result. It is not easy to do the manipulations with 'threaded' array. It would take too many queries to do a single request for the results I need. So, I do only one query, cache it and filter what I need from there and format the arrays after. – bancer Jul 16 '10 at 02:26

3 Answers3

19

I don't think there is a built-in function in PHP that does this.

I tried the following code, and it seems to work to prepare the nested array the way you describe:

$nodes = array();
$tree = array();
foreach ($source as &$node) {
  $node["Children"] = array();
  $id = $node["Menu"]["id"];
  $parent_id = $node["Menu"]["parent_id"];
  $nodes[$id] =& $node;
  if (array_key_exists($parent_id, $nodes)) {
    $nodes[$parent_id]["Children"][] =& $node;
  } else {
    $tree[] =& $node;
  }
}

var_dump($tree);

I wrote a similar algorithm in a PHP class I wrote for my presentation Hierarchical Models in SQL and PHP, but I was using objects instead of plain arrays.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • You are fantastic! Thanks for working solution and sush fast answer! The source array is the result of database query. – bancer Jul 16 '10 at 01:55
  • 1
    Note that this algorithm works only if parents appear up in the db result set before their children appear. – Bill Karwin Jul 16 '10 at 02:05
0

I wrote this variant considering root parent_id is 0 or missing. No matter children after parents in DB ($source) or not.

$source_by_id = array();
foreach ($source as &$row){
  $source_by_id[$row['id']] = &$row;
}
foreach ($source_by_id as $id => &$row){
  $source_by_id[ intval($row['parent_id']) ]['children'][$id] = &$row;
}
// remove cycling itself
unset($source_by_id[0]['children'][0]);

$result = $source_by_id[0]['children'];

Result array keys are appropriate ids. Enjoy!

vitalymak
  • 47
  • 6
0

I was looking for an example of how to do this, with categories. This example assumes that parents will always have a parent id of '0'. The example is using ZF2.

No references, or recursion. The trick is in the output, you look for the [0] index, and for the children, you specify the parent_id as the index.

$categoryLookup = $this->getCategoryLookup($associateById=true);

if ($assignedCategories) {          
    $categoryHeirarchy = array();
    foreach($assignedCategories as $assignedCategory) {
        $child = $categoryLookup[$assignedCategory->category_id];
        $parent = $categoryLookup[$child->parent_id];               
        $categoryHeirarchy[$child->parent_id][] = $categoryLookup[$child->category_id];
        $categoryHeirarchy[$parent->parent_id][$parent->category_id] = $categoryLookup[$parent->category_id];
    }           

    return $categoryHeirarchy;  
}


<h3>Categories</h3>
<dl class="dl-horizontal">
    <?php foreach($this->categoryHeirarchy[0] as $parent): ?>
        <dt><?php echo $this->escapeHtml($parent->name); ?></dt>
        <?php foreach($this->categoryHeirarchy[$parent->category_id] as $child): ?>
            <dd><?php echo $this->escapeHtml($child->name); ?></dd>
        <?php endforeach; ?>
    <?php endforeach; ?>                    
</dl>
slothstronaut
  • 921
  • 1
  • 13
  • 15