Group flat array to multidimensional array by key, If we don't know how many levels are there. PHP

Viewed 613

I have this array:

$arr = [
["id"=>20,
    "name"=>"a",
    "parent"=>28,
],
["id"=>21,
    "name"=>"a-child",
    "parent"=>20,
],
["id"=>27,
    "name"=>"a-child-b",
    "parent"=>20,
],
["id"=>28,
   "name"=>"A parent",
   "parent"=>0,
],
["id"=>12,
    "name"=>"no parent",
    "parent"=>0,
]];

What I want is to group it based on parent key, where parent = id && parent > 0 or the id is the parent for this element and the element has parent, if the parent key is greater than zero.

In the above array id=12 has no parent, id=20 has child 21, 27 and it is a child for id=28.

What I did :

    public function sort($arr){

    $result = [];

    // Get child
    foreach($arr as $key => $row) {
        if($row['parent'] > 0) {
            $result[$row->parent][] = ['id' => $row['id'], 'name' => $row['name']];
            unset($arr[$key]);
        }
    }

    // Get parent and append child
    foreach($arr as $key => $row) {
        $result[$row['id']] = ['name' => $row['name'],
                              'child' => $result[$row['id']]];

    }
    return $result;
}

That problem is that, this is only for 1 level of child like parent => child array().

What I want to make is a method which gets an argument (above array), where I don't know, how many levels of nesting I will have and returns grouped by parent key array:

$arr = [
["id"=>28,
 "name"=>"A parent",
 "parent"=>0,
    'child' => [
        ["id"=>20,
             "name"=>"a",
             "parent"=>28,
             'child' => [
                 ["id"=>21,
                  "name"=>"a-child",
                  "parent"=>20,
                 ],
                 ["id"=>27,
                  "name"=>"a-child-b",
                  "parent"=>20,
                 ]
             ]
        ]
    ]
],
["id"=>12,
 "name"=>"no parent",
 "parent"=>0,
]];
2 Answers

Here's a two-step approach that first builds an associative array mapping parents to arrays of their children, then recursively populates children keys by indexing into the parent-child associative array.

Note that I assume parent => 0 to be the root but this is also adjustable. I also used the key children, which I feel is more semantic, but feel free to revert to child.

function insert(&$curr, $parents) {
    if (array_key_exists($curr['id'], $parents)) {
        $curr['children'] = $parents[$curr['id']];
    }

    if (array_key_exists('children', $curr)) {
        foreach ($curr['children'] as &$child) {
            insert($child, $parents);
        }
    }
}

function treeify($arr) {
    foreach ($arr as $e) {
        $parents[$e['parent']][] = $e;
    }

    foreach ($parents[0] as &$root) {
        insert($root, $parents);
    }

    return $parents[0];
}

Output:

Array
(
    [0] => Array
        (
            [id] => 28
            [name] => A parent
            [parent] => 0
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 20
                            [name] => a
                            [parent] => 28
                            [children] => Array
                                (
                                    [0] => Array
                                        (
                                            [id] => 21
                                            [name] => a-child
                                            [parent] => 20
                                        )

                                    [1] => Array
                                        (
                                            [id] => 27
                                            [name] => a-child-b
                                            [parent] => 20
                                        )

                                )

                        )

                )

        )

    [1] => Array
        (
            [id] => 12
            [name] => no parent
            [parent] => 0
        )

)

Try it!

<?php

define('ROOT_PARENT',0);

function getHierarchy($records){
    $hierarchy = [];

    /*
        let's assume everybody is going to be a parent
    */

    foreach($records as $each_record){
        $each_record['child'] = [];
        $hierarchy[$each_record['id']] = $each_record;
    }

    /*
       Now add child to parent's key in $hierarchy in the 'child' key. 
       The & is important since there may be future childs for current child. So pass by reference is needed
    */
    foreach($records as $each_record){
        $hierarchy[$each_record['parent']]['child'][] = &$hierarchy[$each_record['id']];
    }

    /* 
        here I unset every key which wasn't at root level,i.e is 0(top) level
    */
    foreach($hierarchy as $parent => $its_data){
        if($parent != ROOT_PARENT){
            unset($hierarchy[$parent]);
        }
    }

    return isset($hierarchy[ROOT_PARENT],$hierarchy[ROOT_PARENT]['child']) ? $hierarchy[ROOT_PARENT]['child'] : [];
}

$records = [
            [
                "id" => 20,
                "name" => "a",
                "parent" => 28,
            ],
            [
                "id" => 21,
                "name" => "a-child",
                "parent" => 20,
            ],
            [
                "id" => 27,
                "name" => "a-child-b",
                "parent" => 20,
            ],
            [
                "id" => 28,
               "name" => "A parent",
               "parent" => 0,
            ],
            [
                "id" => 12,
                "name" => "no parent",
                "parent" => 0,
            ]
    ];


echo "<pre>";
print_r(getHierarchy($records));

Output:

Array
(
    [0] => Array
        (
            [id] => 28
            [name] => A parent
            [parent] => 0
            [child] => Array
                (
                    [0] => Array
                        (
                            [id] => 20
                            [name] => a
                            [parent] => 28
                            [child] => Array
                                (
                                    [0] => Array
                                        (
                                            [id] => 21
                                            [name] => a-child
                                            [parent] => 20
                                            [child] => Array
                                                (
                                                )

                                        )

                                    [1] => Array
                                        (
                                            [id] => 27
                                            [name] => a-child-b
                                            [parent] => 20
                                            [child] => Array
                                                (
                                                )

                                        )

                                )

                        )

                )

        )

    [1] => Array
        (
            [id] => 12
            [name] => no parent
            [parent] => 0
            [child] => Array
                (
                )

        )

)

At first, we consider everybody could be a parent. Then, in it's parent's child key, we keep adding it's children. We pass the key by reference since there may be future children. Finally, unset() everybody from the hierarchy that is not a root parent. In the end, you have your final hierarchy structure.

Related