create array tree from array list

95,464

Solution 1

oke this is how i solved it:

$arr = array(
  array('id'=>100, 'parentid'=>0, 'name'=>'a'),
  array('id'=>101, 'parentid'=>100, 'name'=>'a'),
  array('id'=>102, 'parentid'=>101, 'name'=>'a'),
  array('id'=>103, 'parentid'=>101, 'name'=>'a'),
);

$new = array();
foreach ($arr as $a){
    $new[$a['parentid']][] = $a;
}
$tree = createTree($new, array($arr[0]));
print_r($tree);

function createTree(&$list, $parent){
    $tree = array();
    foreach ($parent as $k=>$l){
        if(isset($list[$l['id']])){
            $l['children'] = createTree($list, $list[$l['id']]);
        }
        $tree[] = $l;
    } 
    return $tree;
}

Solution 2

small fix if you need more than 1 parentid[0] element :)

$arr = array(
  array('id'=>100, 'parentid'=>0, 'name'=>'a'),
  array('id'=>101, 'parentid'=>100, 'name'=>'a'),
  array('id'=>102, 'parentid'=>101, 'name'=>'a'),
  array('id'=>103, 'parentid'=>101, 'name'=>'a'),
);

$new = array();
foreach ($arr as $a){
    $new[$a['parentid']][] = $a;
}
$tree = createTree($new, $new[0]); // changed
print_r($tree);

function createTree(&$list, $parent){
    $tree = array();
    foreach ($parent as $k=>$l){
        if(isset($list[$l['id']])){
            $l['children'] = createTree($list, $list[$l['id']]);
        }
        $tree[] = $l;
    } 
    return $tree;
}

Solution 3

One more rework of Thunderstriker's variant - all the logic in one function:

/**
 * @param array $flatList - a flat list of tree nodes; a node is an array with keys: id, parentID, name.
 */
function buildTree(array $flatList)
{
    $grouped = [];
    foreach ($flatList as $node){
        $grouped[$node['parentID']][] = $node;
    }

    $fnBuilder = function($siblings) use (&$fnBuilder, $grouped) {
        foreach ($siblings as $k => $sibling) {
            $id = $sibling['id'];
            if(isset($grouped[$id])) {
                $sibling['children'] = $fnBuilder($grouped[$id]);
            }
            $siblings[$k] = $sibling;
        }
        return $siblings;
    };

    return $fnBuilder($grouped[0]);
}

// Example:
$flat = [
    ['id' => 100, 'parentID' => 0, 'name' => 'root'],
    ['id' => 101, 'parentID' => 100, 'name' => 'ch-1'],
    ['id' => 102, 'parentID' => 101, 'name' => 'ch-1-1'],
    ['id' => 103, 'parentID' => 101, 'name' => 'ch-1-2'],
];

$tree = buildTree($flat, 'parentID', 'id');
echo json_encode($tree, JSON_PRETTY_PRINT);

Playground: https://www.tehplayground.com/Ap4uUuwHWl9eiJIx

Solution 4

Here is my adaptation from arthur's rework:

/* Recursive branch extrusion */
function createBranch(&$parents, $children) {
    $tree = array();
    foreach ($children as $child) {
        if (isset($parents[$child['id']])) {
            $child['children'] =
                $this->createBranch($parents, $parents[$child['id']]);
        }
        $tree[] = $child;
    } 
    return $tree;
}

/* Initialization */
function createTree($flat, $root = 0) {
    $parents = array();
    foreach ($flat as $a) {
        $parents[$a['parent']][] = $a;
    }
    return $this->createBranch($parents, $parents[$root]);
}

Use:

$tree = createTree($flat);

Solution 5

I created an unusual ('while-based' instead of recursive) but multidimensional sorting function that walk the array until there aren't any orphans. Here the function:

function treeze( &$a, $parent_key, $children_key )
{
    $orphans = true; $i;
    while( $orphans )
    {
        $orphans = false;
        foreach( $a as $k=>$v )
        {
            // is there $a[$k] sons?
            $sons = false;
            foreach( $a as $x=>$y )
            if( isset($y[$parent_key]) and $y[$parent_key]!=false and $y[$parent_key]==$k )  
            { 
                $sons=true; 
                $orphans=true; 
                break;
            }

            // $a[$k] is a son, without children, so i can move it
            if( !$sons and isset($v[$parent_key]) and $v[$parent_key]!=false )
            {
                $a[$v[$parent_key]][$children_key][$k] = $v;
                unset( $a[$k] );
            }
        }
    }
}

Recommendation: the key of each element of the array has to be the id fo the element itself. Example:

$ARRAY = array(
    1 => array( 'label' => "A" ),
    2 => array( 'label' => "B" ),
    3 => array( 'label' => "C" ),
    4 => array( 'label' => "D" ),
    5 => array( 'label' => "one", 'father' => '1' ),
    6 => array( 'label' => "two", 'father' => '1' ),
    7 => array( 'label' => "three", 'father' => '1' ),
    8 => array( 'label' => "node 1", 'father' => '2' ),
    9 => array( 'label' => "node 2", 'father' => '2' ),
    10 => array( 'label' => "node 3", 'father' => '2' ),
    11 => array( 'label' => "I", 'father' => '9' ),
    12 => array( 'label' => "II", 'father' => '9' ),
    13 => array( 'label' => "III", 'father' => '9' ),
    14 => array( 'label' => "IV", 'father' => '9' ),
    15 => array( 'label' => "V", 'father' => '9' ),
);

Usage: the function need $a (the array), $parent_key (the name of the column where the id of the father is saved), $children_key (the name of the column where the children will be move). It returns nothing (the array is changed by reference). Example:

treeze( $ARRAY, 'father', 'children' );
echo "<pre>"; print_r( $ARRAY );
Share:
95,464

Related videos on Youtube

Thunderstriker
Author by

Thunderstriker

Updated on March 31, 2021

Comments

  • Thunderstriker
    Thunderstriker about 3 years

    i have a list like this:

    array(
      array(id=>100, parentid=>0, name=>'a'),
      array(id=>101, parentid=>100, name=>'a'),
      array(id=>102, parentid=>101, name=>'a'),
      array(id=>103, parentid=>101, name=>'a'),
    )
    

    but way bigger so i need a efficient way to make this into a tree like structure like this:

    array(
      id=>100, parentid=>0, name=>'a', children=>array(
        id=>101, parentid=>100, name=>'a', children=>array(
          id=>102, parentid=>101, name=>'a',
          id=>103, parentid=>101, name=>'a',
        )
      )
    )
    

    i cannot use things like nested set or things like that becoas i can add left and right values in my database. any ideas?

  • erisco
    erisco over 13 years
    The problem with this algorithm is that it is likely O(n^2). Consider an array where every element is the parent of the next. This algorithm would scan the array n times, resulting in n(n+1)/2 operations.
  • DampeS8N
    DampeS8N over 13 years
    So remove the items from the old array as you find them before passing it along. My intention here was just to get a sketch of a function that will do the job. Not do the job fast. That's for optimization later on. This is the web. Caching is a better place to expend these kinds of mental energies.
  • erisco
    erisco over 13 years
    My calculation of n(n+1)/2 operations accounted for the fact that the array is shrinking after every scan. The OP stated that his data structure was "way bigger"; I feel it is implied that O(n^2) is too expensive.
  • Thunderstriker
    Thunderstriker over 13 years
    i was doing it like this until my tree where becomming to big that is way i asked for an efficient way it to mo about 1 second to create the tree and had to load several of them so this solution is to slow. i posted my solution this ony will do it in 5ms
  • Adam
    Adam about 13 years
    This works well, just make sure that, if you have more than one item with parentid=0, to loop through all the items and check for parentid == 0. If that's true, then run createTree on that item and append it to your tree array. Otherwise, this routine only works for the first item where parentid=0
  • mike_hornbeck
    mike_hornbeck over 11 years
    Any ideas how to get this one working with any parentId on the lowest level ? stackoverflow.com/questions/11942115/…
  • Peter Krauss
    Peter Krauss over 10 years
    Good way, faster than recurrence! Needs a little fix, "Notice: Undefined index: father"
  • Marco Panichi
    Marco Panichi over 8 years
    @PeterKrauss I've fixed the notice (and also improve a little bit the formatting)
  • Altenrion
    Altenrion almost 8 years
    Found this solusion, helped a lot!
  • Jin
    Jin over 7 years
    Question: Is the &$list required or can it also work with $list? I believe that's what PHP uses to pass by ref. Also, is recursion discouraged in PHP?
  • srayner
    srayner over 7 years
    I liked this example so I wrapped it in a class and made it available on github here; github.com/srayner/NavTree
  • Oleg Abrazhaev
    Oleg Abrazhaev about 7 years
    what if we will have a loop here?
  • 472084
    472084 about 7 years
    @JinIzzraeel If you want to pass non-objects by ref, you need the ampersand. Recursion is not discouraged in PHP.
  • Happy Coder
    Happy Coder over 6 years
    How to add an element to this tree as a child of a particular parent?
  • Vasily
    Vasily over 6 years
    @HappyCoder just add an element to the $flat, for example ['id'=>103, 'parentID'=>101, 'name'=>'a'] - it's a child of a ['id'=>101, 'parentID'=>100, 'name'=>'a'] element
  • Disorder
    Disorder about 6 years
    man you saved me from pulling out my hair today. thanks a lot.
  • Admin
    Admin over 5 years
    personally, I'd make $array immutable and just add parentless items to a new array then return that array.
  • Alan Delval
    Alan Delval about 5 years
    It works perfectly with multiple root parents with id 0.
  • seniorpreacher
    seniorpreacher over 4 years
    Based on @Vasily 's answer, I made an "array of instances" tree builder: gist.github.com/seniorpreacher/64adfcf4844974b568bc84bf3056c‌​05e
  • Sithell
    Sithell about 3 years
    What is idKey? My ide keeps complaining about it
  • Vasily
    Vasily about 3 years
    @Sithell, that was an extra unused variable, deleted it, thanks!