PHP -通过父属性将平面关联数组转换为深度嵌套数组

tpgth1q7  于 2023-01-29  发布在  PHP
关注(0)|答案(1)|浏览(106)

我有一个问题,已经强调了我几个星期了,我不能找到一个干净的解决方案,它不涉及递归。
这就是问题所在:取一个嵌套关联数组的平面数组,并将其组合成一个深嵌套对象。该对象的顶层将其parent属性设置为null。
这是我的解决方案,但我承认它远非完美。我相当肯定这可以在一个单一的循环中完成,没有任何递归,但我的生活,我不能解决它!

//Example single fork
$data = array(

    //Top of Tree
    0 => array(
        "name" => "A",
        "parent" => null,
        "id" => 1,
    ),

    //B Branch
    1 => array(
        "name" => "B",
        "parent" => "1",
        "id" => 2,
    ),
    2 => array(
        "name" => "B1",
        "parent" => "2",
        "id" => 3,
    ),
    3 => array(
        "name" => "B2",
        "parent" => "3",
        "id" => 4,
    ),
    4 => array(
        "name" => "B3",
        "parent" => "4",
        "id" => 5,
    ),

    //C Branch
    5 => array(
        "name" => "C",
        "parent" => "1",
        "id" => 6,
    ),
    6 => array(
        "name" => "C1",
        "parent" => "6",
        "id" => 7,
    ),
    7 => array(
        "name" => "C2",
        "parent" => "7",
        "id" => 8,
    ),
    8 => array(
        "name" => "C3",
        "parent" => "8",
        "id" => 9,
    ),

);
Actual anonymised example
array:7214 [▼
  0 => array:3 [▼
    "name" => ""
    "parent" => null
    "id" => 
  ]
  1 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  2 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  3 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  4 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  5 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  6 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  7 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  8 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  9 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
  10 => array:3 [▼
    "name" => ""
    "parent" => 
    "id" => 
  ]
Another example deeper nesting 
{
   "name":"top",
   "id":xxx,
   "children":{
      "second":{
         "name":"second",
         "id":xxx,
         "children":{
            "Third":{
               "name":"third",
               "id":xxx,
               "children":{
                  "fourth":{
                     "name":"fourth",
                     "id":xxx
                  }
               }
            }
         }
      }
   }
}
$originalLength = count($data);
$obj = [];
while ($originalLength > 0) {
    foreach ($data as $item) {
        $name = $item['name'];
        $parent = $item['parent'];

        $a = isset($obj[$name]) ? $obj[$name] : array('name' => $name, 'id'=>$item['id']);

        if (($parent)) {

            $path = get_nested_path($parent, $obj, array(['']));
            try {
                insertItem($obj, $path, $a);
            } catch (Exception $e) {
                continue;
                //echo 'Caught exception: ', $e->getMessage(), "\n";
            }
        }

        $obj[$name] = isset($obj[$name]) ? $obj[$name] : $a;
        $originalLength--;
    }
}

echo json_encode($obj['A']);
function get_nested_path($parent, $array, $id_path)
{

    if (is_array($array) && count($array) > 0) {

        foreach ($array as $key => $value) {
            $temp_path = $id_path;

            array_push($temp_path, $key);

            if ($key == "id" && $value == $parent) {
                array_shift($temp_path);
                array_pop($temp_path);
                return $temp_path;
            }

            if (is_array($value) && count($value) > 0) {
                $res_path = get_nested_path(
                    $parent, $value, $temp_path);

                if ($res_path != null) {
                    return $res_path;
                }
            }
        }
    }
    return null;
}

function insertItem(&$array, $path, $toInsert)
{
    $target = &$array;
    foreach ($path as $key) {
        if (array_key_exists($key, $target))
            $target = &$target[$key];
        else throw new Exception('Undefined path: ["' . implode('","', $path) . '"]');
    }

    $target['children'] = isset($target['children']) ? $target['children'] : [];
    $target['children'][$toInsert['name']] = $toInsert;
    return $target;
}
6rqinv9w

6rqinv9w1#

以下是我对我所认为的理想输出的看法:

function buildTree(array $items): ?array {

    // Get a mapping of each item by ID, and pre-prepare the "children" property.
    $idMap = [];
    foreach ($items as $item) {
        $idMap[$item['id']] = $item;
        $idMap[$item['id']]['children'] = [];
    }

    // Store a reference to the treetop if we come across it.
    $treeTop = null;

    // Map items to their parents' children array.
    foreach ($idMap as $id => $item) {
        if ($item['parent'] && isset($idMap[intval($item['parent'])])) {
            $parent = &$idMap[intval($item['parent'])];
            $parent['children'][] = &$idMap[$id];
        } else if ($item['parent'] === null) {
            $treeTop = &$idMap[$id];
        }
    }

    return $treeTop;
}

这将执行两个数组循环,一个循环是按IDMap数据,另一个循环是将子项分配给父项。

  • 在第一个循环中构建$idMap也有效地复制了这里的项,所以我们不会影响原始的输入数组(除非它已经包含引用)。
  • 在第二个循环中,使用&来引用其他项,否则默认情况下PHP会在赋值时创建一个副本,因为这些项是数组(PHP在赋值时复制数组,这与PHP中的Object或JavaScript等其他语言中的数组不同)。这允许我们在结构中有效地共享同一个数组“item”。
  • 这并不能防止错误的输入,输入数据中的无效Map或循环引用可能会导致问题,尽管我们的函数应该总是只执行两个循环,所以至少不应该陷入无限/穷举循环。

相关问题