skip to Main Content

I need to generate a multidimensional array based on a "map" of letters

my array :

$list = [
  0 => [
    'name' => 'blah',
    'path' => 'A'
  ],
  1 => [
    'name' => 'blah',
    'path' => 'AA'
  ],
  2 => [
    'name' => 'blah',
    'path' => 'AB'
  ],
  3 => [
    'name' => 'blah',
    'path' => 'B'
  ],
  4 => [
    'name' => 'blah',
    'path' => 'BA'
  ],
  5 => [
    'name' => 'blah',
    'path' => 'BAA'
  ],
];

but I need this:

$list = [
  0 => [
    'name' => 'blah',
    'path' => 'A',
    'childs' => [
      0 => [
        'name' => 'blah',
        'path' => 'AA'
      ],
      1 => [
        'name' => 'blah',
        'path' => 'AB'
      ],
    ]
  ],
  3 => [
    'name' => 'blah',
    'path' => 'B',
    'childs' => [
      0 => [
        'name' => 'blah',
        'path' => 'BA',
        'childs' => [
          0 => [
            'name' => 'blah',
            'path' => 'BAA'
          ],
        ]
      ],
    ]
  ],
];

I’m going to need this array to be in a way that is easy to manipulate, but I’m not able to use the "&" in a foreach so that I can generate an array at least close to what I put above.

sorry if i asked the question incorrectly… my english is bad and it’s my first time here

5

Answers


  1. Chosen as BEST ANSWER

    my solution:

    $list = [
        ['name' => 'blah1', 'path' => 'A'],
        ['name' => 'blah2', 'path' => 'AA'],
        ['name' => 'blah3', 'path' => 'AB'],
        ['name' => 'blah4', 'path' => 'B'],
        ['name' => 'blah5', 'path' => 'BA'],
        ['name' => 'blah5', 'path' => 'BB'],
        ['name' => 'blah6', 'path' => 'BAA'],
        ['name' => 'blah6', 'path' => 'BAB'],
        ['name' => 'blah6', 'path' => 'BAC'],
    ];
    
    
    $data = [];
    $arr = &$data;
    foreach ($list as $item){
    
        $letters = str_split($item['path']);
    
        $i = 1;
        foreach ($letters as $letter){
    
            $path = substr($item['path'], 0, $i);
    
            if (!isset($arr[$path])){
                $arr[$path] = $item;
                $arr[$path]['childs'] = [];
            }
    
            $arr = &$arr[$path]['childs'];
    
            $i++;
        }
    
        $arr = &$data;
    }
    
    

  2. Because there weren’t much clues on the question on how to actually do the desired grouping. I’ll assume, based on the provided output sample, that the grouping will be based on the first letter found in the values where the key is path. The task will basically be solved using array_reduce() built-in function:

    Iteratively reduce the array to a single value using a callback function. Source: php.net

    To maintain a performant solution, we’ll index the resulting array based on the letters we found (the letters we’ll group using them). Like so we will iterate over the $list array only once.

    The trick here is to decide what to do at every iteration of the array_reduce function:

    • When it’s the first time we encounter a new letter, let’s call that letter $letter, then we will construct an array (based on your output sample) where $letter is the key.
    • Otherwise, if we have already the letter in the resulting array we will simply merge the children of the resulting array of the $letter key.

    Using the letters as keys will drastically improve the solution’s performance because we can simply say $result[$letter] and this will immediately return the array found on the $letter key (O(1)).

    To illustrate, here’s a code sample that should get the job done:

    $list = [
        ['name' => 'blah', 'path' => 'A'],
        ['name' => 'blah', 'path' => 'AA'],
        ['name' => 'blah', 'path' => 'AB'],
        ['name' => 'blah', 'path' => 'B'],
        ['name' => 'blah', 'path' => 'BA'],
        ['name' => 'blah', 'path' => 'BAA'],
    ];
    
    $result = array_reduce($list, function ($a, $c) {
        $l = substr($c['path'], 0, 1);
        isset($a[$l])
            ? $a[$l]['children'][] = $c
            : $a[$l] = [
            'name' => $c['name'],
            'path' => $c['path'],
            'children' => []
        ];
        return $a;
    }, []);
    

    The $result array will have the letters as keys, if you need to remove those letters and use numerical keys, you may call

    And I have made a live demo too.

    Login or Signup to reply.
  3. I would loop through the $list giving each item a ['childs'] array then copy into a $new_list based on the path using the letters as keys in the array. I accomplish this below by constructing a string to evaluate with eval().

    $new_list = [];
    foreach( $list as $item ){
        $item['childs'] = [];
        $ev = '$new_list';
        foreach(str_split($item['path']) as $i => $p){
            if(0 == $i){
              $ev .= "['$p']"; // $new_list['A']
            }else{
                $ev .= "['childs']['$p']"; // $new_list['A']['childs']['A'] ... ['childs']['B'] etc.
            }
        }
        $ev .= ' = $item;'; // $new_list['A']['childs']['A'] = $item;
        eval($ev);
    }
    
    var_export($new_list);
    

    The above code outputs the following:

    array (
      'A' => 
      array (
        'name' => 'blah',
        'path' => 'A',
        'childs' => 
        array (
          'A' => 
          array (
            'name' => 'blah',
            'path' => 'AA',
            'childs' => 
            array (
            ),
          ),
          'B' => 
          array (
            'name' => 'blah',
            'path' => 'AB',
            'childs' => 
            array (
            ),
          ),
        ),
      ),
      'B' => 
      array (
        'name' => 'blah',
        'path' => 'B',
        'childs' => 
        array (
          'A' => 
          array (
            'name' => 'blah',
            'path' => 'BA',
            'childs' => 
            array (
              'A' => 
              array (
                'name' => 'blah',
                'path' => 'BAA',
                'childs' => 
                array (
                ),
              ),
            ),
          ),
        ),
      ),
    )
    

    See it in action here: https://onlinephp.io/c/cf8d0

    Note:

    This works because you have all ancestor-items defined in $list before their descendants. If the order of items varies (like 'AAB' before 'AA') then you will need to add some checks to see if the $new_list['A']['childs']['A']['childs'] array exists yet and construct it when necessary.

    Login or Signup to reply.
  4. This recursive approach seeks out qualifying rows, pushes them as children of the desired parent row and unsets all rows that are used as children of a parent (somewhere). The new array structure is not returned; the input array is modified by reference.

    Code: (Demo)

    function rowsToTree(&$array, $parent = '') {
        $children = [];
        foreach ($array as $i => &$row) {
            if ($parent === substr($row['path'], 0, -1)) {
                // recurse for children
                $found = rowsToTree($array, $row['path']);
                // only declare the children element if has children
                if ($found) {
                    $row['children'] = $found;
                    // if not a top-level parent remove the row from the original array which has been pushed into the children element
                    if ($parent) {
                        unset($array[$i]);
                    }
                }
                // collect all children in this level for this parent (not top level) to be returned to 8ts parent then remove row from original array
                if ($parent) {
                    $children[] = $row;
                    unset($array[$i]);
                }
            }
        }
        return $children;
    }
    rowsToTree($rows);
    var_export($rows);
    
    Login or Signup to reply.
  5. Here is a non-recursive approach that does provides the result in a new variable by sorting the array with deepest children first then using a nested loop to brute-force search for parents.

    Code: (Demo)

    $copy = $rows;
    uasort($rows, fn($a, $b) => $b['path'] <=> $a['path']);
    foreach ($rows as $i => $row1) {
        foreach ($rows as $j => &$row2) {
            if (substr($row1['path'], 0, -1) === $row2['path']) {
                $copy[$j]['children'][] = $row1;
                unset($copy[$i]);
            }
        }
    }
        
    var_export($copy);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search