提问人:James Baird 提问时间:10/5/2022 最后编辑:James Baird 更新时间:1/25/2023 访问量:431
PHP - 按父属性将平面关联数组转换为深度嵌套数组
PHP - Flat Associative Array into Deeply nested Array by parent property
问:
我有一个问题,几个星期以来一直困扰着我,我找不到一个不涉及递归的干净解决方案。
这就是问题所在: 采用一个嵌套关联数组的平面数组,并将其分组到一个深度嵌套的对象中。此对象的顶层将使其父属性为 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;
}
答:
3赞
Dan Brown
10/5/2022
#1
以下是我对我认为是所需输出的看法:
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;
}
这将执行两个数组周期,一个按 ID 映射数据,另一个将子项分配给父项。需要注意的一些关键要素:
- 第一个循环中的构建也有效地复制了这里的项目,因此我们不会影响原始输入数组(除非它已经包含引用)。
$idMap
- 在第二个循环中,可以使用对其他项目的引用,否则默认情况下,PHP会在赋值时有效地创建一个副本,因为这些是数组(PHP在赋值时复制数组,这与PHP中的对象或其他一些语言(如JavaScript)中的数组不同)。这使我们能够在整个结构中有效地共享相同的数组“项”。
&
- 这不能防止错误的输入。输入数据中的无效映射或循环引用可能会导致问题,尽管我们的函数应该始终只执行两个循环,因此至少不应该陷入无限/穷举循环。
评论
1赞
James Baird
10/5/2022
你太棒了!我知道这是可以做到的,没有回避!绝对的传奇!!
0赞
James Baird
10/10/2022
虽然以上很精彩,但它似乎确实有一个缺陷。数组中的对象似乎被覆盖了吗?我之所以做出这个假设,是因为原始数据中的某些项目不再存在于输出结果中!令人 沮丧!但是你的回答确实表明,这可以在没有递归的情况下实现,它还没有完美。
0赞
Dan Brown
10/11/2022
@JamesBaird 你指的是实际的物体吗?如果是这样,它们的行为会有所不同。此解决方案是根据您提供的示例输入构建的,其中没有对象。否则,您是否有一个输入失败的输入示例?根据您的示例输入,我在输出中看到了所有这些项目。
0赞
James Baird
10/13/2022
我将避免使用“对象”一词,因为没有对象。这是一个关联数组。我还举了两个从实际数据源中提取并匿名的例子。我可以使您的解决方案产生某些条目不存在的结果。我在第四层观察到了这一点。我不明白为什么......
0赞
James Baird
10/13/2022
我只是一次又一次地运行它,得到了一个完全不同的结果,缺少条目。这是最奇怪的事情。我知道我的代码不必要地冗长和 OTT,但结果总是一样的。为什么没有递归的解决方案似乎失败了??
上一个:从数组到外部变量的链接?
评论
very resource heavy
...所以它很慢,或者使用大量内存,或者两者兼而有之?为了让它按照您想要的方式工作,您期望的参数是什么?是否有最大执行时间和/或内存使用量和/或您认为可以接受的任何其他内容?到目前为止,你有没有尝试过任何优化方法?