【问题标题】:Counting (child) elements in arrays计算数组中的(子)元素
【发布时间】:2016-05-19 15:27:42
【问题描述】:

我有一个对象数组。每个对象如下所示:

{text: 'foo': level: N: children: M}

NM 都是数字。 N 表示对象的“深度”位置(将整个数组想象成一棵树的表示)。 M 是该项目的子项总数。 Total 表示所有子层的所有节点和叶子的总和。

我拥有的数组中的对象不包含children 属性,我必须计算它。

“计算”数据示例:

{text: 'A': level: 0: children: 0}
{text: 'B': level: 0: children: 1}
{text: 'C': level: 1: children: 0}
{text: 'D': level: 0: children: 2}
{text: 'E': level: 1: children: 1}
{text: 'F': level: 2: children: 0}

A
B
|--C
D
|--E
   |--F

我可以轻松地做一个for 循环,然后在项目上再迭代一次,从数组中的当前位置开始计算子项(基于 level 属性),但我觉得这不是最好(如“最快”)的方式。

我还能怎么做?

只是为了确保没有人发布比我目前拥有的“更糟糕”的代码,这就是我所拥有的:

for (var i = 0; i < data.length; i++) {
    var item = data[i];

    for (var j = i + 1; j < data.length; j++) {
        if (item.level < data[j].level) {
            item.children++;
        } else {
            break;
        }
    }
}

【问题讨论】:

  • 请添加您的代码和一些数据。
  • @NinaScholz 什么数据?我已经发布了与该问题相关的所有数据。什么代码?我解释为非禁食代码的那个?
  • @Rajesh 请务必理解我的问题。我不知道孩子的价值(这是我应该计算的)。
  • 顺便说一句,您的示例数据中的 : 冒号太多

标签: javascript arrays


【解决方案1】:

在您当前的示例中,您根本无法计算子项,因为您的数据不显示父/子关系。例如,level:1 用于 text: 'C',如果应将其附加(即增加 children 计数器)到 ABD,则没有数据。

如果你的意思是,但忘了提到子对象应该附加到最近提到的任何最近的父对象,那么只需保留一个数组,其中每个条目都对应于对该级别的最后一次看到的父元素的引用,并将其修改为 @ 987654327@ 属性与+= 在读取每个新行后直接。

【讨论】:

  • A 和 B 处于同一级别,因此 C 不能是两者的子级。它只能是 B 的孩子。所以 B 的孩子数应该增加。
  • RE 到您的编辑:我不能这样做,因为当我在 F 行时,我不会引用 D。我只能增加E。换句话说,我不能在树中向后和向上递增。
  • “保留一个数组”。当读取带有level: 2 的条目时,您会将子项添加到两个条目中:known_parents[0].children++; known_parents[1].children++; 在循环中,当然,不是这样逐字逐句的。
【解决方案2】:

这是我认为@Oleg V. Volkov 在他的评论中提出的答案。我认为这是一个非常好的解决问题的方法,而且速度非常快,因为您只查看每个元素 1 次。但是,不利的一面是,由于父数组的关系,它在计算时可能会占用更多内存。

function countChildren(tree) {
    var currentParents = [];

    for (var i = 0; i < tree.length; i++) {
        // Initialize value of children
        tree[i].children = 0;

        if (i == 0) {
            continue;
        }

        // Find if the level increased, decreased, or stayed the same.
        var levelChange = tree[i].level - tree[i-1].level;

        // If level increased (by 1)
        if (levelChange == 1) {
            // Add previous element to list of parents
            currentParents.push(tree[i-1]);

        // If level decreased by 'x'
        } else if (levelChange < 0) {
            // Remove 'x' number of elements from the end of the list of parents
            for (var j = 0; j > levelChange; j--) {
                currentParents.pop();
            }
        }

        // Increase the number of children for all parents
        for (var j = 0; j < currentParents.length; j++) {
            currentParents[j].children++;
        }
    }
}

【讨论】:

  • 我可以告诉你,你甚至没有测试过那个代码 :) 你混合了强类型语法 (for (int i...) 并且你混合了几个级别的 i
  • 原谅我,但您实际上从未提供过样本数据供我使用,所以我只是在概念上对其进行了编程。
  • 数据在我的问题中。查看Example of calculated data 并删除children 属性。
  • 好的,我使用了你的数据,我可以确认这个算法现在可以正常工作了。另外,由于您的编辑,我删除了其他答案,因为是的,它与您现在使用的基本相同。但是,使用我建议的嵌套对象算法仍然是最优雅的,因为它最不容易出现格式错误的数据。 (例如跳过一个级别)。此外,执行查找父母或添加新元素等其他事情所需的代码也少得多。鉴于您的用例,它可能不适合,但对于大多数情况,它是处理这种嵌套数据的最佳方式。
  • 我取消选择您的答案是正确的,因为经过一些测试后发现我的解决方案更快。你知道为什么会这样吗?
【解决方案3】:

这是一种不同的方法,与所有其他解决方案不同,从底部开始。

优点是,每当达到较小的水平时,就会取总和并将实际水平累加器设置为零。

此解决方案仅具有一个循环和一个数组count 用于级别累积。

我用这个数据做了一个更大的测试(属性children除外,这是算法的结果)。

{ text: 'A', level: 0, children:  0 },    // A
{ text: 'B', level: 0, children:  1 },    // B
{ text: 'C', level: 1, children:  0 },    //   C
{ text: 'D', level: 0, children:  3 },    // D
{ text: 'E', level: 1, children:  2 },    //   E
{ text: 'F', level: 2, children:  0 },    //     F
{ text: 'F', level: 2, children:  0 },    //     G
{ text: 'H', level: 0, children: 18 },    // H
{ text: 'I', level: 1, children:  5 },    //   I
{ text: 'J', level: 2, children:  1 },    //     J
{ text: 'K', level: 3, children:  0 },    //       K
{ text: 'L', level: 2, children:  2 },    //     L
{ text: 'M', level: 3, children:  1 },    //       M
{ text: 'N', level: 4, children:  0 },    //         N
{ text: 'O', level: 1, children: 10 },    //   O
{ text: 'P', level: 2, children:  9 },    //     P
{ text: 'Q', level: 3, children:  7 },    //       Q
{ text: 'R', level: 4, children:  5 },    //         R
{ text: 'S', level: 5, children:  2 },    //           S
{ text: 'T', level: 6, children:  0 },    //             T
{ text: 'U', level: 6, children:  0 },    //             U
{ text: 'V', level: 5, children:  0 },    //           V
{ text: 'W', level: 5, children:  0 },    //           W
{ text: 'X', level: 4, children:  0 },    //         X
{ text: 'Y', level: 3, children:  0 },    //       Y
{ text: 'Z', level: 1, children:  0 },    //   Z

var data = [{ text: 'A', level: 0 }, { text: 'B', level: 0 }, { text: 'C', level: 1 }, { text: 'D', level: 0 }, { text: 'E', level: 1 }, { text: 'F', level: 2 }, { text: 'F',level: 2 }, { text: 'H', level: 0 }, { text: 'I', level: 1 }, { text: 'J', level: 2 }, { text: 'K', level: 3 }, { text: 'L', level: 2 }, { text: 'M', level: 3 }, { text:'N', level: 4 }, { text: 'O', level: 1 }, { text: 'P', level: 2 }, { text: 'Q', level: 3 }, { text: 'R', level: 4 }, { text: 'S', level: 5 }, { text: 'T', level: 6 }, {text: 'U', level: 6 }, { text: 'V', level: 5 }, { text: 'W', level: 5 }, { text: 'X', level: 4 }, { text: 'Y', level: 3 }, { text: 'Z', level: 1 }],
    i = data.length,
    count = [];
	
while (i--) {
    if (data[i].level > 0) {
        count[data[i].level - 1] = (count[data[i].level - 1] || 0) + (count[data[i].level] || 0) + 1;
    }
    data[i].children = count[data[i].level] || 0;
    count[data[i].level] = 0;
}

document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');

带有预填充数组count(不检查未定义)和级别变量的版本。

var data = [{ text: 'A', level: 0 }, { text: 'B', level: 0 }, { text: 'C', level: 1 }, { text: 'D', level: 0 }, { text: 'E', level: 1 }, { text: 'F', level: 2 }, { text: 'F',level: 2 }, { text: 'H', level: 0 }, { text: 'I', level: 1 }, { text: 'J', level: 2 }, { text: 'K', level: 3 }, { text: 'L', level: 2 }, { text: 'M', level: 3 }, { text:'N', level: 4 }, { text: 'O', level: 1 }, { text: 'P', level: 2 }, { text: 'Q', level: 3 }, { text: 'R', level: 4 }, { text: 'S', level: 5 }, { text: 'T', level: 6 }, {text: 'U', level: 6 }, { text: 'V', level: 5 }, { text: 'W', level: 5 }, { text: 'X', level: 4 }, { text: 'Y', level: 3 }, { text: 'Z', level: 1 }],
    i = data.length, l,
    count = Array.apply(null, { length: 50 }).map(function () { return 0; });
	
while (i--) {
    l = data[i].level;
    if (l) {
        count[l - 1] += count[l] + 1;
    }
    data[i].children = count[l];
    count[l] = 0;
}

document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');

【讨论】:

  • 非常好!如果数组的最后一项是/不在级别 0,那会起作用吗?我相信“是”,因为您编写示例数据的方式。
  • @alexandernst,显然是的。
  • 好的,我测试了你的解决方案。它确实输出了正确的值,但它比我目前拥有的要慢(我的问题中的解决方案)。你知道为什么会这样吗?
  • 如果您知道深度,您可以预填充计数数组。 l = data[i].level; 的分配应该使它更快。请参阅编辑。如果这无助于恢复原始数组应该使其更快,迭代器应该上升。
  • 我进行了更多的测试,但这总是比我的解决方案慢。
【解决方案4】:

在没有for 循环的情况下,我能想到的唯一方法是要求您重新调整树结构。 JavaScript 对象可以嵌套,所以我建议使用如下格式:

[
    {
        text: 'A',
        children: []
    },
    {
        text: 'B',
        children: 
        [
            {
                text: 'C',
                children: []
            }
        ]
     },
     {
         text: 'D',
         children:
         [
             {
                 text: 'E',
                 children:
                 [
                     {
                         text: 'F',
                         children: []
                     }
                 ]
             }
         ]
     }
]

那么对于每个对象,“孩子总数”的公式将类似于:

function countChildren(obj) {
    var count = obj.children.length;
    for (int i = 0; i < children.length; i++) {
        count += countChildren(children[i]);
    }
    return count;
}

【讨论】:

  • 这不会计算嵌套的孩子。
  • @alexandernst 好地方!我修复了它,使其递归执行。
  • 这与我所拥有的几乎相同,只是您使用递归函数解决了它,而不是嵌套的 for 循环。我怀疑这有什么性能改进。
  • @alexandernst 是的,这是一项非常简单的任务,答案之间确实不会有太多差异。在我看来,这是最优雅的解决方案。
  • 您的解决方案仅适用于一小部分数据。我正在处理的数据量很容易超过 node/chrome 的递归限制。因此,我看不出这是最优雅的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-18
  • 2021-10-10
  • 1970-01-01
  • 2016-03-24
  • 1970-01-01
相关资源
最近更新 更多