简体   繁体   English

递归计算对象 function

[英]Calculating on objects in recursive function

I'm trying to make a function that calculates the percentage of "completed tasks" in a recursive manner.我正在尝试制作一个 function 以递归方式计算“已完成任务”的百分比。

Here's what I have now, but this only calculates the percentage on the parent, not grandparent etc:这是我现在所拥有的,但这只计算父母的百分比,而不是祖父母等:

 const tasks = [ { id: 1, task: 'Clean apartment', completed: null, parentId: null}, { id: 2, task: 'Make app', completed: null, parentId: null}, { id: 3, task: 'Clean bathroom', completed: null, parentId: 1}, { id: 4, task: 'Clean kitchen', completed: null, parentId: 1}, { id: 5, task: 'Wash sink', completed: true, parentId: 3}, { id: 6, task: 'Wash shower', completed: null, parentId: 3}, { id: 7, task: 'Wash glass panes', completed: null, parentId: 6}, { id: 8, task: 'Wash floor', completed: null, parentId: 6}, ] function compileTask(tasks, taskId) { var task = (typeof taskId === 'number')? tasks.find(task => task.id === taskId): taskId; var children = tasks.filter(t => t.parentId === task.id); task.percent = children.filter(c => c.completed === true).length / children.length * 100 task.children = children.map(child => compileTask(tasks, child)); return task } console.log(compileTask(tasks, 1))

As you see, task id 3 is 50% completed because one of the two child tasks are marked completed, but task id 1 is 0% completed.如您所见,任务 ID 3 已完成 50%,因为两个子任务之一已标记为已完成,但任务 ID 1 已完成 0%。 If I'm calculating right in my head, task id 1 should return 25% completed.如果我在脑海中计算正确,任务 id 1 应该返回 25% 完成。

Any tips?有小费吗?

You are mixing objects and integers.您正在混合对象和整数。

var task = (typeof taskId === 'number') ? tasks.find(task => task.id === taskId) : taskId;

returns a task in one case and an id in another在一种情况下返回任务,在另一种情况下返回 id

then here然后在这里

task.children = children.map(child => compileTask(tasks, child));

You are passing a child instead of the child.id .您正在传递一个孩子而不是child.id It should be它应该是

task.children = children.map(child => compileTask(tasks, child.id));

I'm not quite sure of the output format you want, so I'm going to generate something like the following.我不太确定你想要的 output 格式,所以我将生成如下内容。 We can probably alter it to fit your needs:我们可能会更改它以满足您的需求:

[
  {id: 1, task: 'Clean apartment', percent: 25, parentId: null}
  {id: 2, task: 'Make app', percent: 0, parentId: null}
  {id: 3, task: 'Clean bathroom', percent: 50, parentId: 1}
  {id: 4, task: 'Clean kitchen', percent: 0, parentId: 1}
  {id: 5, task: 'Wash sink', percent: 100, parentId: 3}
  {id: 6, task: 'Wash shower', percent: 0, parentId: 3}
  {id: 7, task: 'Wash glass panes', percent: 0, parentId: 6}
  {id: 8, task: 'Wash floor', percent: 0, parentId: 6}
]

Initial Approach初始方法

This is a fairly simple recursive technique:这是一个相当简单的递归技术:

 const sum = (ns) => ns.reduce ((a, b) => a + b, 0) const findPercent = (task, tasks, children = tasks.filter (({parentId}) => task.id == parentId)) => children.length? sum (children.map (c => findPercent(c, tasks))) / children.length: task.completed? 100: 0 const compileTasks = (tasks) => tasks.map ( (task, _, __, {completed, parentId, ...rest} = task) => ({...rest, percent: findPercent (task, tasks), parentId }) ) const tasks = [{id: 1, task: 'Clean apartment', completed: null, parentId: null}, {id: 2, task: 'Make app', completed: null, parentId: null}, {id: 3, task: 'Clean bathroom', completed: null, parentId: 1}, {id: 4, task: 'Clean kitchen', completed: null, parentId: 1}, {id: 5, task: 'Wash sink', completed: true, parentId: 3}, {id: 6, task: 'Wash shower', completed: null, parentId: 3}, {id: 7, task: 'Wash glass panes', completed: null, parentId: 6}, {id: 8, task: 'Wash floor', completed: null, parentId: 6}] console.log (compileTasks (tasks))
 .as-console-wrapper {max-height: 100%;important: top: 0}

findPercent is a helper function that recursively looks through the children, and averages their percentages. findPercent是一个助手 function,它递归地查看子项,并对它们的百分比进行平均。 It bottoms out when there are no children, and then chooses 0% or 100% depending upon whether the task is completed .它在没有孩子时触底,然后根据任务是否completed选择 0% 或 100%。 It uses a trivial sum helper, which adds up an array of numbers.它使用了一个普通的sum助手,它将一个数字数组相加。

The main function is compileTasks .主要的 function 是compileTasks It simply maps the list of tasks to slightly altered versions, removing completed and adding percent , which is found by calling findPercent .它只是将任务列表映射到略有改动的版本,删除completed并添加percent ,这是通过调用findPercent找到的。

There is a potential problem with this technique, though.但是,这种技术存在一个潜在问题。 It doesn't matter at all for small tests, but there is a lot of work that is done multiple times.对于小测试来说完全没有关系,但是有很多工作是多次完成的。 We average the percentages of 7 and 8, when building 1, then do it again when building 3, and again when building 6. This is problematic, and so I will suggest a:我们在构建 1 时将 7 和 8 的百分比取平均值,然后在构建 3 时再次计算,然后在构建 6 时再次计算。这是有问题的,因此我建议:

More Performant Approach更高效的方法

The problem here is that our flat list is not the best way to track what is fundamentally a tree structure.这里的问题是我们的平面列表不是跟踪树结构的最佳方式。 This would be much easier to work with:这会更容易使用:

[
    {id: 1, task: "Clean apartment", completed: null, children: [
        {id: 3, task: "Clean bathroom", completed: null, children: [
            {id: 5, task: "Wash sink", completed: true},
            {id: 6, task: "Wash shower", completed: null, children: [
                {id: 7, task: "Wash glass panes", completed: null},
                {id: 8, task: "Wash floor", completed: null}
            ]}
        ]},
        {id: 4, task: "Clean kitchen", completed: null}
    ]},
    {id: 2, task: "Make app", completed: null}
]

I would suggest that you consider using this more useful structure internally all the time, and only using the flat list for storage.我建议你考虑一直在内部使用这个更有用的结构,并且只使用平面列表进行存储。 But if you can't, do this, then we can convert back and forth between the two structure, and do your calculations on that nicer tree structure.但如果你不能,就这样做,然后我们可以在两种结构之间来回转换,并在更好的树结构上进行计算。 It might look like this:它可能看起来像这样:

 const sum = (ns) => ns.reduce ((a, b) => a + b, 0) const nest = (tasks, parent = null) => tasks.filter (({parentId}) => parentId === parent).map ( ({id, parentId, ...rest}, _, __, children = nest (tasks, id)) => ({ id, ...rest, ...(children.length? {children}: {}) })) const unnest = (nodes, parentId = null) => [...nodes.map (({children, ...rest}) => ({...rest, parentId})), ...nodes.flatMap (node => unnest (node.children || [], node.id)) ] const calcPcts = (tasks) => tasks.map (({completed, children = [], ...rest}, _, __, kids = calcPcts (children)) => ({... rest, ... (kids.length? {percent: sum (kids.map (k => k.percent)) / kids.length, children: kids}: {percent: completed? 100: 0} ) })) const compileTasks = (tasks) => unnest (calcPcts (nest (tasks))) const tasks = [{id: 1, task: 'Clean apartment', completed: null, parentId: null}, {id: 2, task: 'Make app', completed: null, parentId: null}, {id: 3, task: 'Clean bathroom', completed: null, parentId: 1}, {id: 4, task: 'Clean kitchen', completed: null, parentId: 1}, {id: 5, task: 'Wash sink', completed: true, parentId: 3}, {id: 6, task: 'Wash shower', completed: null, parentId: 3}, {id: 7, task: 'Wash glass panes', completed: null, parentId: 6}, {id: 8, task: 'Wash floor', completed: null, parentId: 6}] console.log (compileTasks (tasks))
 .as-console-wrapper {max-height: 100%;important: top: 0}

Here, after the sum helper, we have a nest function that converts your flat list into that tree structure, and an unnest function that converts it such a tree back into the flat list.在这里,在sum助手之后,我们有一个nest function 将您的平面列表转换为该树结构,以及一个unnest function 将这样的树转换回平面列表。

Our main function is compileTasks , which simply composes these two functions and runs the core function calcPcts between them to generate this intermediate format:我们主要的 function 是compileTasks ,它简单地组合了这两个函数并运行它们之间的核心 function calcPcts以生成这种中间格式:

[
    {id: 1, task: "Clean apartment", percent: 25, children: [
        {id: 3, task: "Clean bathroom", percent: 50, children: [
            {id: 5, task: "Wash sink", percent: 100},
            {id: 6, task: "Wash shower", percent: 0, children: [
                {id: 7, task: "Wash glass panes", percent: 0},
                {id: 8, task: "Wash floor", percent: 0}
            ]}
        ]},
        {id: 4, task: "Clean kitchen", percent: 0}
    ]},
    {id: 2, task: "Make app", percent: 0}
]

And now we need to discuss calcPcts .现在我们需要讨论calcPcts This works much like above, except that because we're calling top-down, and rolling up the results from the bottom up, we don't have to re-call it for nested nodes.这与上面的工作方式非常相似,除了因为我们自上而下调用并从下向上滚动结果,所以我们不必为嵌套节点重新调用它。

One interesting thing about this approach is that you can reuse nest and unnest around other recursive node conversion techniques.这种方法的一个有趣之处在于,您可以围绕其他递归节点转换技术重用nestunnest The roll-up from completed to percent is isolated in that one place, but the overall mechanism is shared.completedpercent的汇总在那个地方是隔离的,但整体机制是共享的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM