【问题标题】:Reducing an Array of unknown length to a nested Object将未知长度的数组减少为嵌套对象
【发布时间】:2018-11-28 18:36:01
【问题描述】:

我正在尝试获取一个数组并从中创建一个嵌套对象,其中数组中的每个项目都是前一个项目的属性。

我认为reduce 是做到这一点的方法,但我发现reduce 很难掌握,而且我尝试的每件事都卡在知道如何进入下一个级别。 JS: Reduce array to nested objects 是一个类似的问题,但我尝试了很多变体后仍然无法解决。

const myArray = ['one', 'two', 'three'];

// Intended Output (note, the staticCount is always 1)
{
    one: {
        staticCount: 1,

        two: {
            staticCount: 1,

            three: {
                staticCount: 1
            }
        }
    }
}

【问题讨论】:

    标签: javascript


    【解决方案1】:

    有些工作需要Array.prototype.reduceRight

    const myArray = ['one', 'two', 'three']
    
    const nestNode = (acc, key) => {
      acc.staticCount = 1
      return { [key]: acc }
    }
    console.log(myArray.reduceRight(nestNode, {}))

    让我们来看看reduceRight(以及,扩展为reduce):

    我将迭代器函数定义从对reduceRight 的调用中移出,以使该示例更易于讨论(请参阅nestNode)。

    reducereduceRight 相似:

    1. 每个参数都有两个参数,一个迭代器函数和一个该函数累加器的初始值。第二个参数是可选的,但在这里我将忽略它。

    2. Each 遍历数组中调用它们的所有项,为数组中的每个项调用迭代器函数,其中包含四个参数、累加器、数组中的当前项、当前迭代count 和您调用 reduce 的整个数组。最后两个参数在这里不相关(我很少使用它们)。

    3. 第一次调用迭代器函数时,会将您提供的第二个参数传递给reducereduceRight(初始累加器值)。之后,它将传递上一步中迭代器函数返回的任何内容。

    因为我认为reduce(以及扩展名reduceRight)是值得理解的强大抽象,我将逐步介绍代码示例中的前两个步骤:

    1. 在迭代的第一步,我们的迭代器函数是这样调用的:nestNode(acc = {}, key = 'three')。在nestNode 中,我们将staticCount 属性添加到acc 并将其设置为1,从而得到acc = { staticCount: 1 }。然后我们创建并返回一个新对象,其属性名为'three',其值等于acc。第一步nestNode返回的值是{ three: { staticCount: 1 } },第二步会用这个值调用nestNode

    2. 在迭代的第二步,我们的迭代器函数是这样调用的:nestNode(acc = { three: { staticCount: 1 } }, key = 'two')。同样,我们将staticCount 属性添加到acc 并将其设置为1,从而得到acc = { three: { staticCount: 1 }, staticCount: 1 }。然后我们创建并返回一个新对象,其属性名为'two',其值等于acc。我们返回的值是{ two: { three: { staticCount: 1 }, staticCount: 1 } }。同样,此值将在下一步中使用。

    我将跳过最后一步,因为我希望详细了解前两个步骤足以让事情变得更清楚。如果您还有其他问题或仍有不清楚或令人困惑的地方,请告诉我。

    reduce(和reduceRight)是功能强大、灵活的工具,值得学习和熟悉。

    作为结尾,我将在每一步之后为您提供迭代器函数的返回值:

    1. { three: { staticCount: 1 } }

    2. { two: { three: { staticCount: 1 } }, staticCount: 1 }

    3. { one: { two: { three: { staticCount: 1 } }, staticCount: 1 }, staticCount: 1 }

    【讨论】:

    • @MarkMeyer 谢谢,马克,我解决了这个问题。
    • @Tex 这太好了,谢谢 - 我从你的回答中学到了很多!每个答案(在撰写本文时)都是正确的解决方案,所以我必须接受一个,然后我选择了我认为最易读的一个。
    • 在阅读了你所有的 cmets 之后,我确实认为这最好地回答了这个问题,并以 正确 的方式使用 reduce。
    【解决方案2】:

    感谢 Tex 让我将 reverse().reduce() 替换为 reduceRight

    ['one', 'two', 'three'].reduceRight((a, c) => ({[c]: { staticCount: 1, ...a }}), {});

    【讨论】:

    • 这可能是我大多数时候使用的解决方案。它比我的reduceRight 示例慢一点(因为对象传播和创建一个额外的新对象而不是分配给现有对象),但它也非常紧凑且易于阅读。如果我有理由相信性能差异会成为我的应用程序中的一个问题,我只会考虑其中一个更快的示例。
    【解决方案3】:

    reducemap 一样,将遍历数组中的每个项目并返回结果。主要区别在于map 将返回一个大小与原始数组相同的数组,并进行任何修改。 reduce 接受所谓的 accumulator 并将其作为最终结果返回。

    reduce() 有两个参数:

    1. function()
    2. accumulator 的起始值

    您提供的function() 被赋予三个值:

    1. accumulator 的值
    2. 数组中当前项的值
    3. 当前迭代的值(本例中未使用)
    4. 原始数组的值(本例中未使用)

    了解accumulator 最重要的一点是,它将成为您的function() 返回的任何值,并且您的函数ALWAYS 必须返回一些东西,否则accumulator 将在下一个循环中未定义。

    按照您的问题的解决方案是使用reduce 的基本示例。

    解决方案

    const myArray = ['one','two','three'];
    const result = {};
    
    myArray.reduce((accumulator, num) => {
      accumulator[num] = { staticCount: 1}
      return accumulator[num];
    }, result);
    
    console.log(result);

    性能

    这里提供的reduce 解决方案每秒可以执行 760 万次操作,而reduceRight 每秒可以执行 220 万次操作。

    https://jsperf.com/reduceright-vs-reduce/

    基本示例

    var numbers = [1, 2, 3, 4, 5];
    
    // Reduce will assign sum whatever
    // the value of result is on the last loop
    
    var sum = numbers.reduce((result, number) => {
      return result + number;
    }, 0); // start result at 0
    
    console.log(sum);

    另一个例子

    var numbers = [1, 2, 3, 4, 5];
    
    // Here we're using the iterator, and
    // assinging "too much" to sum if there
    // are more than 4 numbers.
    
    var sum = numbers.reduce((result, number, i) => {
      if (i >= 4) return "too much";
      return result + number;
    }, 0);
    
    console.log(sum);

    文档

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

    【讨论】:

    • 这里的性能差异可能是由于reduce()版本依赖于外部状态(结果)。 reduceRight() 版本似乎更实用。
    • 我想这取决于您的用例。就我个人而言,我会比 220 万多取 760 万,即使 reduceRight 是这个任务更合适命名的函数。
    • 在这种情况下,我会联系reduceRight,除非我知道这将在差异很重要的热门路径中运行。我会这样做是因为我认为reduceRight 版本更容易理解并且正确使用reduce 抽象,而不是作为美化的forEach。如果后来我意识到性能是一个问题,我会考虑使用更高效的reduce 变体。感谢分享性能测试 - 结果也让我感到惊讶。
    • 关于(过早的)优化:不能保证reduce 解决方案将来会比reduceRight 解决方案更快。在某些时候它甚至可能会更慢。有时我会陷入不必要地从我的例程中挤出最后一滴性能的诱惑,但通常情况下编译器最终会比我聪明,找到击败或匹配我已经意识到的性能提升的方法通过可能使我的代码更难推理的技巧,而不是从一开始就选择更合适的抽象。
    【解决方案4】:

    只需使用reduce。它之所以有效,是因为对象是通过引用传递的。

    const myArray = ['one', 'two', 'three'];
    const newObject = {};
    
    myArray.reduce((acummulator, element) => {
      acummulator[element] = {
        staticCount: 1
      };
      return acummulator[element];
    }, newObject);
    
    // Intended Output (note, the staticCount is always 1)
    
    console.log(newObject);

    阅读有关减少 here.

    【讨论】:

      【解决方案5】:

      诀窍不是从空对象开始,而是从某个声明的变量开始。然后只需将新的孩子作为下一次递归的聚合向下传递。

      引用已更新,然后您可以再次打印根目录。

      const myArray = ['one', 'two', 'three'];
      const root = {};
      myArray.reduce( (agg, c) => {
        agg[c] = { staticCount: 1 };
        return agg[c];
      }, root );
      
      console.log( root );

      【讨论】:

        【解决方案6】:

        这也可以通过递归函数来实现:

        const createObj = (keys) => keys.length > 0 && ({
          [keys[0]]: {
            staticCount: 1,
            ...createObj(keys.slice(1))
          }
        });
        
        console.log(createObj(['one', 'two', 'three']));

        【讨论】:

        • 干得好!这是我的递归解决方案:const createObj = ([head, ...tail]) => head !== undefined && (tail.length === 0 ? {[head]:{staticCount:1}} : {[head]: {staticCount: 1, ...createObj(tail)}})
        猜你喜欢
        • 1970-01-01
        • 2018-05-30
        • 2018-01-12
        • 1970-01-01
        • 1970-01-01
        • 2019-07-26
        • 2019-10-16
        • 1970-01-01
        • 2020-09-18
        相关资源
        最近更新 更多