SO,
问题
假设我们有平面数组,结构如下:
$array = [
['level'=>1, 'name' => 'Root #1'],
['level'=>1, 'name' => 'Root #2'],
['level'=>2, 'name' => 'subroot 2-1'],
['level'=>3, 'name' => '__subroot 2-1/1'],
['level'=>2, 'name' => 'subroot 2-2'],
['level'=>1, 'name' => 'Root #3']
];
问题是 - 转换该数组,使其成为一棵树。仅通过元素顺序和level
字段确定从属关系。让我们将children
定义为用于存储子节点的维度名称。对于上面的数组,将是:
array ( array ( 'level' => 1, 'name' => 'Root #1', ), array ( 'level' => 1, 'name' => 'Root #2', 'children' => array ( array ( 'level' => 2, 'name' => 'subroot 2-1', 'children' => array ( array ( 'level' => 3, 'name' => '__subroot 2-1/1', ), ), ), array ( 'level' => 2, 'name' => 'subroot 2-2', ), ), ), array ( 'level' => 1, 'name' => 'Root #3', ), )
稍微澄清一点,如果不明确谁是谁的父母:以下代码可以很容易地想象出想法:
function visualize($array)
{
foreach($array as $item)
{
echo(str_repeat('-', $item['level']).'['.$item['name'].']'.PHP_EOL);
}
}
visualize($array);
- 对于它上面的数组:
-[Root #1] -[Root #2] --[subroot 2-1] ---[__subroot 2-1/1] --[subroot 2-2] -[Root #3]
具体细节信息
对于所需的解决方案和输入数组都有一些限制:
foreach
(或另一个循环 - 无所谓),只需要处理一次,每个元素都应该被处理。我的方法
目前,我有堆栈解决方案。我正在使用引用并维护当前步骤中将要写入的当前堆栈元素。那就是:
function getTree(&$array)
{
$level = 0;
$tree = [];
$stack = [&$tree];
foreach($array as $item)
{
if($item['level']>$level) //expand stack for new items
{
//if there are child elements, add last to stack:
$top = key($stack);
if(count($stack[$top]))
{
end($stack[$top]);
$stack[] = &$stack[$top][key($stack[$top])];
}
//add ['children'] dim to top stack element
end($stack);
$top = key($stack);
$stack[$top]['children'] = [];
$stack[] = &$stack[$top]['children'];
}
while($item['level']<$level--) //pop till certain level
{
//two times: one for last pointer, one for ['children'] dim
array_pop($stack);
array_pop($stack);
}
//add item to stack top:
end($stack);
$stack[key($stack)][] = $item;
$level = $item['level'];
}
return $tree;
}
- 因为它已经足够长了,我已经创建了一个sample的用法&amp;输出
问题
正如您所看到的,我的解决方案很长,它依赖于参考资料和数组内部指针处理(例如end()
),所以问题是:
可能还有其他 - 更短更清晰的方法来解决这个问题?它看起来像是一些标准问题,但是我没有找到任何相应的解决方案(有一个类似的question - 但OP确实有parent_id
从属关系,而我还没有)
答案 0 :(得分:2)
关于您的问题的好处是您的输入始终格式正确,因此您的实际问题将缩小到为每个节点查找子节点(如果存在)或为每个节点查找父节点(如果有)。后者更适合这里,因为我们知道如果节点的级别大于1,则节点具有父节点,并且它是初始平面数组中位于其上方的最近节点,其级别等于当前节点的级别减1。根据这一点,我们可以跟踪我们感兴趣的几个节点。更准确地说,每当我们找到两个具有相同级别的节点时,之前找到的节点就不能有更多的子节点。
这样做的实现如下:
function buildTree(array &$nodes) {
$activeNodes = [];
foreach ($nodes as $index => &$node) {
//remove if you don't want empty ['children'] dim for nodes without childs
$node['children'] = [];
$level = $node['level'];
$activeNodes[$level] = &$node;
if ($level > 1) {
$activeNodes[$level - 1]['children'][] = &$node;
unset($nodes[$index]);
}
}
}
答案 1 :(得分:1)
使用递归的实现:
function buildTreeHelper(&$array, $currentLevel = 1)
{
$result = array();
$lastIndex = 0;
while($pair = each($array)) {
list(, $row) = $pair;
$level = $row['level'];
if ($level > $currentLevel) {
$result[$lastIndex]['children'] = buildTreeHelper($array, $level);
} else if ($level == $currentLevel) {
$result[++$lastIndex] = $row;
} else {
prev($array); // shift back
break;
}
}
return $result;
}
function buildTree($array)
{
reset($array);
return buildTreeHelper($array);
}
$array = [
['level'=>1, 'name' => 'Root #1'],
['level'=>1, 'name' => 'Root #2'],
['level'=>2, 'name' => 'subroot 2-1'],
['level'=>3, 'name' => '__subroot 2-1/1'],
['level'=>2, 'name' => 'subroot 2-2'],
['level'=>1, 'name' => 'Root #3']
];
print_r(buildTree($array));