【问题标题】:Optimal way to convert an Array of Objects into an Array of Arrays containing Objects将对象数组转换为包含对象的数组数组的最佳方法
【发布时间】:2018-07-24 18:33:26
【问题描述】:

以这个极其简化的对象数组为例:

[
    {
      createID: '1'
      // Many other properties...
    },
    {
      createID: '1'
      // Many other properties...
    },
    {
      createID: '1'
      // Many other properties...
    },
    {
      createID: '37'
      // Many other properties...
    },
    {
      createID: '37'
      // Many other properties...
    },
    {
      createID: '2'
      // Many other properties...
    },
    {
      createID: '2'
      // Many other properties...
    },
    {
      createID: '14'
      // Many other properties...
    },
  ];

给定这个数组,然后我使用对象createID 属性创建一个包含对象[[{..},{..}], [{..}], ..n] 的数组数组。我正在使用的当前前端框架(Angular v6)需要这种最终格式。

为了完成这项任务,我使用以下代码,其中tempArr 是一个类似于上面提供的示例数组的数组。

    let currentGroup: string = tempArr[0].createID;
    let tempGrouped: any[] = [];
    let childGroup: any[] = [];
    tempArr.forEach(item => {
      if (item.createID !== currentGroup) {
        tempGrouped.push(childGroup);
        childGroup = [];
        currentGroup = item.createID;
      }
      childGroup.push(item);
    });
    tempGrouped.push(childGroup);

此代码运行良好。但是,我不禁相信必须有一种更高效、更优雅的方法,将对象数组转换为包含对象的数组。

更新

请务必注意,createID只是表示应将哪些对象组合在一起的 id。 因此,它们不需要按createID 进行数字排序。此外,正如您在提供的给定示例数组中所见,这些对象确实来自与其兄弟对象(相同的createID)“分组”的服务器。

【问题讨论】:

  • 如果您可以为 stackblitz 提供一个工作示例,我们将更容易提供工作替代方案。 :-)
  • 更优雅?使用for … of 而不是.forEach。更高效?不,你所拥有的是最佳的。
  • 具有相同 ID 的对象是否总是彼此相邻?
  • @MarkMeyer,是的,他们会的 - 在问题中查看我的更新。

标签: javascript angular performance typescript for-loop


【解决方案1】:

您的示例具有彼此相邻的所有相同 ID。如果保证始终如此,那么您只需要循环并推送到新数组即可。但是,如果不是这种情况,您的解决方案将无法正确分组项目。在这种情况下,使用哈希表将允许您仍然按具有相同渐近复杂度的 ID 进行分组。

您可以使用从createdID 创建的键将您的对象分组到一个哈希表对象中。这将使您有效地对所有内容进行分组。然后从哈希表中取出对象:

let arr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];

let o = arr.reduce((a, c) => {
    (a[c.createID] || (a[c.createID] = [])).push(c)
    return a
}, {} )
// o is a an object with createID keys pointing to arrays of grouped objects
// just take the values
console.log(Object.values(o))

根据问题编辑编辑

由于对象已经被分组,没有比循环遍历更好的方法了。如果您想要一个不添加临时数组的选项,您仍然可以使用reduce(),这与您当前的解决方案基本相同,但可能更独立:

let tempArr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];

let r = tempArr.reduce((a, c, i, self) => {
    if (i === 0 || self[i-1].createID !== c.createID) 
        a.push([])
    a[a.length - 1].push(c)
    return a
}, [])

console.log(r)

【讨论】:

  • 哈希表更复杂,当组的顺序正确时不需要。这可能比 OP 当前使用的效率低。
  • @Bergi OPs 解决方案看起来 O(n**2) 并且依赖于几个不必要的临时数组。怎么这么简单?
  • OPs 解决方案是线性的,而不是O(n²)
  • @Bergi 我的错误我虽然他们是按 id 对对象进行分组而不考虑顺序——如果它不依赖于已经分组的对象,它将是 O(n**2)。具有相同 ID 但不相邻的当前解决方案对象最终位于不同的数组中。
  • 这太棒了,尽管您的第二个回答确实更具体地回答了这个问题 - 我必须承认我喜欢您的第一个回答使用哈希表时的紧凑、简单和健壮性。感谢您的回复和时间!
【解决方案2】:

假设您的数据数组存储在名为data 的变量中:

  const result = data.reduce((acc, current) => {
    if (!acc.dictionary[current.createID]) {
      const createIdArray = [];
      acc.dictionary[current.createID] = createIdArray;
      acc.array.push(createIdArray);
    }

    acc.dictionary[current.createID].push(current);

    return acc;
  }, {array: [], dictionary: {}}).array;

这样,您只需对数据循环一次,而且效率很高,因为我们不使用过滤器或查找(它会一次又一次地遍历整个数组)。

这是输出:

[
  [
    {
      createID: '1',
    },
    {
      createID: '1',
    },
    {
      createID: '1',
    },
  ],
  [
    {
      createID: '37',
    },
    {
      createID: '37',
    },
  ],
  [
    {
      createID: '2',
    },
    {
      createID: '2',
    },
  ],
  [
    {
      createID: '14',
    },
  ],
];

这是一个正在运行的演示:https://stackblitz.com/edit/typescript-phbzug

总结:

  • 字典是自包含在 reduce 函数中的,这意味着一旦 reduce 完成,它就会被垃圾回收
  • 不依赖任何外部变量,更容易推理和 IMO 更好的做法
  • 这个解决方案更健壮(数组不需要排序)~与 OP 的答案相同的行数
  • 干净:使用字典,您可以直接了解您正在访问的内容,而且速度非常快

【讨论】:

  • 为什么要使用字典?这些组的顺序已经正确。
  • 谁告诉你这些组的顺序是“正确的”? OP从来没有这么说。另外,我正在处理它不是的情况,只循环一次,并且对字典的引用是自包含在 reduce 函数中的,这意味着一旦它运行,它将被垃圾收集。我在这里没有看到任何问题。也就是说,您有更好的解决方案可以分享吗? :)
  • OP 表示他们的解决方案正在运行,因此他们似乎认为这些组是有序的。他们目前的解决方案也只循环一次,而且可能更快。如果您认为字典方法更好(确实可能),请编辑您的答案以说明您的代码在哪些情况下会产生不同的结果。
  • OP 可能只循环一次,但它也依赖于 3 个“外部”变量,我宁愿避免。然后,OP 解决方案不太健壮,因为它假设数据按给定的顺序(可能是),但是对于尽可能多的行,您可以获得更健壮的解决方案,所以为什么不呢。推理起来并不复杂。使用字典,您可以直接知道要访问的内容,而且速度非常快。
  • 请将其添加到您的答案中。
【解决方案3】:

您想按 createID 分组吗?

let grouped=tempArray
//first get uniq values
.filter((s,index)=>tempArray.findIndex(f=>f.createID==s.createID)==index)
//then, with the uniq values make a map       
.map(seg=>{  //with each uniq value, create an object with two properties
   return {
      createID:seg.createID,  //the key
      items:tempArray.filter(s=>s.createID==seg.createID) //An array with the values
   }
})

【讨论】:

  • 使用过滤器,findIndex和过滤器没有优化。
  • 是的,它很优雅,允许对象数组不排序,但肯定有更高效的方法。我谷歌并在hackernoon.com/… 中我们可以看到过滤和查找效率不高
  • 甚至不谈性能,仅仅阅读第一个过滤器就需要相当多的时间......而且它甚至不会输出 OP 所期望的。尝试做一个stackblitz :)
猜你喜欢
  • 2021-04-21
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-06
  • 2012-12-01
  • 2015-01-03
相关资源
最近更新 更多