我正在尝试创建一个简单的树结构,其中每个任务都有一定的完成百分比,其父级必须继承其直接子级的平均完成率,如下图所示。 (0是完成的百分比,例如,子任务2可以是100%而子任务2 0%,这将使task1 50%完成,因此如果task2为0,则stackoverflow将具有25%)
我遇到的问题是,我显然需要从最深的孩子开始,但我似乎无法弄清楚如何实现从叶子到根的这种逆转遍历。
我尝试过普通的递归和双循环,两者都只实现了第一级计算(在图片示例中,task1计算,但stackoverflow将保持为0)。
注意:只有叶子实际上可以具有完成百分比,因为每个其他元素(不是叶子)都会从其子元素继承百分比。 (多么自相矛盾)
如果您对如何实现此类算法有任何想法,无论是概念还是实际代码,我都非常感谢任何输入。
以下是此数组的结构(仅保留相关信息):
[0] => Array
(
[title] => stackoverflow
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => task2
[completion] => 0
)
[1] => Array
(
[title] => task1
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => subtask2
[completion] => 100
)
[1] => Array
(
[title] => subtask1
[completion] => 0
)
)
)
)
)
我似乎遇到的问题与此问题中的问题类似:Percentages and trees但是,我需要我的任务才能获得实际百分比,而不仅仅是已完成/未完成。所有数学都是完全线性的,这意味着父母的百分比=(加上所有儿童百分比)/(儿童数)
var_export:
array (
0 =>
array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
)
答案 0 :(得分:2)
这是一个递归函数,它通过引用传递父级,直到它找到一个叶子并更新向后工作的总数。
function completionTree(&$elem, &$parent=NULL) {
// Handle arrays that are used only as a container... if we have children but no uuid, simply descend.
if (is_array($elem) && !isset($elem['uuid'])) {
foreach($elem AS &$child) {
completionTree($child, $elem);
}
}
// This array has children. Iterate recursively for each child.
if (!empty($elem['children'])) {
foreach ($elem['children'] AS &$child) {
completionTree($child, $elem);
}
}
// After recursion to handle children, pass completion percentages up to parent object
// If this is the top level, nothing needs to be done (but suppress that error)
if (@$parent['completed'] !== NULL) {
// Completion must be multiplied by the fraction of children it represents so we always add up to 100. Since values are coming in as strings, cast as float to be safe.
$parent['completed'] = floatval($parent['completed']) + (floatval($elem['completed']) * (1/count($parent['children'])));
}
}
// Data set defined statically for demonstration purposes
$tree = array(array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '100',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
);
// Launch recursive calculations
completionTree($tree);
// Dump resulting tree
var_dump($tree);
答案 1 :(得分:1)
虽然这已得到解答,但我想留下一个似乎有点更直观的解决方案(恕我直言)。不要传递父母,而是先处理孩子:
/**
* @param array $nodes
*
* @return array
*/
function calcCompletion(array $nodes): array {
// for each node in nodes
return array_map(function (array $node): array {
// if it has children
if (array_key_exists('children', $node) && is_array($node['children'])) {
// handle the children first
$node['children'] = calcCompletion($node['children']);
// update this node by *averaging* the children values
$node['completed'] = array_reduce($node['children'], function (float $acc, array $node): float {
return $acc + floatval($node['completed']);
}, 0.0) / count($node['children']);
}
return $node;
}, $nodes);
}
答案 2 :(得分:0)
嗯,这可能有点开销,但您可以使用RecursiveArrayIterator
。首先,您必须扩展它以处理树结构:
class MyRecursiveTreeIterator extends RecursiveArrayIterator
{
public function hasChildren()
{
return isset($this->current()['children'])
&& is_array($this->current()['children']);
}
public function getChildren()
{
return new static($this->current()['children']);
}
}
然后使用RecursiveIteratorIterator
你可以创建一个迭代器,它将从树叶开始处理你的树:
$iterator = new RecursiveIteratorIterator(
new MyRecursiveTreeIterator($tasks),
RecursiveIteratorIterator::CHILD_FIRST
);
然后使用这个,您可以添加计算逻辑:
$results = [];
$temp = [];
$depth = null;
foreach ($iterator as $node) {
if ($iterator->getDepth() === 0) {
// If there were no children use 'completion'
// else use children average
if (
is_null($depth)
|| !isset($temp[$depth])
|| !count($temp[$depth])
) {
$percentage = $node['completed'];
} else {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
}
$results[$node['title']] = $percentage;
continue;
}
// Set empty array for current tree depth if needed.
if (!isset($temp[$iterator->getDepth()])) {
$temp[$iterator->getDepth()] = [];
}
// If we went up a tree, collect the average of children
// else push 'completion' for children of current depth.
if ($iterator->getDepth() < $depth) {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
$temp[$depth] = [];
$temp[$iterator->getDepth()][] = $percentage;
} else {
$temp[$iterator->getDepth()][] = $node['completed'];
}
$depth = $iterator->getDepth();
}
这是a demo。