基于父ID值将数组从一维转换为多维

6
我有一个一维对象数组,表示多维数据:
array(
    array(
        "id" => 45,
        "parent_id" => null
    ),
    array(
        "id" => 200,
        "parent_id" => 45
    ),
    array(
        "id" => 345,
        "parent_id" => 45
    ),
    array(
        "id" => "355",
        "parent_id" => 200
    )
);

我应该如何将它转换为多维数组:
array(
    array(
        "id" => 45,
        "parent_id" => null,
        "children" => array(
            array(
                "id" => 200,
                "parent_id" => 45,
                "children" => array(
                    "id" => "355",
                    "parent_id" => 200
                )

            ),
            array(
                "id" => 345,
                "parent_id" => 45
            ),
        )
    ),
);

数据是否有序,子节点总是在父节点之后? - Blem
1
@Blem,不是的。顺便说一句,那是个好问题。 - Emanuil Rusev
2个回答

5
以下代码示例将数组$array转换为您所需的树形结构:
// key the array by id
$keyed = array();
foreach($array as &$value)
{
    $keyed[$value['id']] = &$value;
}
unset($value);
$array = $keyed;
unset($keyed);

// tree it
$tree = array();
foreach($array as &$value)
{
    if ($parent = $value['parent_id'])
        $array[$parent]['children'][] = &$value;
    else
        $tree[] = &$value;
}
unset($value);
$array = $tree;
unset($tree);

var_dump($array); # your result

如果存在父级id为0,则此方法无效。但可以轻松更改以反映这一点。

这是一个相关的问题,原始数组已经被键掉了,因此可以省略解决方案的前半部分:嵌套数组。第三层消失了

编辑:

那么这是如何工作的呢?这是利用PHP变量别名(也称为引用)和(临时)数组来存储a)节点别名($keyed)和b)构建新树形顺序的别名($tree)。

你能[...]解释一下$array = $keyed$array = $tree以及未设置的目的吗?

由于$keyed$tree都包含对$array中值的引用,因此我首先将该信息复制到$array中,例如:

$array = $keyed;

现在,$keyed仍然被设置(并且包含与$array中相同的值的引用),因此将取消设置$keyed

unset($keyed);

这将取消 $keyed 中的所有引用,并确保 $array 中的所有值不再被引用(值的引用计数减少一)。
如果迭代后未取消临时数组,则它们的引用仍然存在。如果在 $array 上使用 var_dump,则会看到所有值都有一个 &,因为它们仍然被引用。 unset($keyed) 删除这些引用,再次运行 var_dump($array),您将看到 & 已经消失了。
希望这样可以理解,如果您不熟悉引用,有时可能很难跟踪。对我来说,把它们想象成变量别名通常会有所帮助。
如果您想要一些练习,请考虑以下内容:

如何使用一次 foreach 迭代将您的 $array 从平面转换为树形结构?

请自行决定何时单击包含解决方案的链接。

太棒了!你能否添加注释来解释 $array = $keyed$array = $treeunset 的目的? - Emanuil Rusev
@Emanuil Rusev:我添加了一部分关于赋值和取消赋值的内容。如果你喜欢,还有一个小练习。让我知道;实际上,这个练习的解决方案也是对你的问题的有效答案,因此它可以在以后作为参考添加到答案中。 - hakre
@EmanuilRusev:在另一个问题中,PHP变量:引用还是副本,我也更详细地回答了关于数组和引用的问题。这可能对你有些趣味,所以我在这里留下了评论。 - hakre

2
function convertArray ($array) {
  // First, convert the array so that the keys match the ids
  $reKeyed = array();
  foreach ($array as $item) {
    $reKeyed[(int) $item['id']] = $item;
  }
  // Next, use references to associate children with parents
  foreach ($reKeyed as $id => $item) {
    if (isset($item['parent_id'], $reKeyed[(int) $item['parent_id']])) {
      $reKeyed[(int) $item['parent_id']]['children'][] =& $reKeyed[$id];
    }
  }
  // Finally, go through and remove children from the outer level
  foreach ($reKeyed as $id => $item) {
    if (isset($item['parent_id'])) {
      unset($reKeyed[$id]);
    }
  }
  return $reKeyed;
}

我确信这可以简化为只有两个循环(将第二个和第三个合并),但是现在我无论如何都想不出来...
注意:此函数依赖于“parent_id”作为没有父级的项目的值,其值可以是NULL或根本未设置,因此在最后一个循环中isset()返回FALSE。

第三个循环取消所有项目 :? - Emanuil Rusev
它只取消设置parent_id未设置或设置为NULL(因此isset()返回FALSE)的项目。如果您的实际数据不是这种情况,则需要修改最后一个循环中的if,使其使用唯一适用于没有父级的项目的条件。如果我在提供的示例数据上运行它,则会产生您期望的结果。 - DaveRandom
谢谢Dave!那正是问题所在。我已经处理好了,现在看起来一切都正常了。再次感谢! - Emanuil Rusev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接