按数组中的对象分组最有效的方法是什么?

例如,给定此对象数组:

[ 
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
    { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
    { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
    { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
    { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]

我正在表格中显示这些信息。我想通过不同的方法进行分组,但我想对值求和。

我将Undercore.js用于其groupby函数,这很有用,但并不能完成全部任务,因为我不希望它们“拆分”,而是“合并”,更像SQL groupby方法。

我要找的是能够合计特定值(如果需要)。

因此,如果我按阶段分组,我希望收到:

[
    { Phase: "Phase 1", Value: 50 },
    { Phase: "Phase 2", Value: 130 }
]

如果我组了阶段/步骤,我会收到:

[
    { Phase: "Phase 1", Step: "Step 1", Value: 15 },
    { Phase: "Phase 1", Step: "Step 2", Value: 35 },
    { Phase: "Phase 2", Step: "Step 1", Value: 55 },
    { Phase: "Phase 2", Step: "Step 2", Value: 75 }
]

是否有一个有用的脚本,或者我应该坚持使用Undercore.js,然后遍历生成的对象,自己计算总数?


当前回答

您可以从array.reduce()构建ES6映射。

const groupedMap = initialArray.reduce(
    (entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
    new Map()
);

这与其他解决方案相比有一些优势:

它不需要任何库(与例如_.groupBy()不同)您得到的是JavaScript Map而不是对象(例如,由_.groupBy()返回)。这有很多好处,包括:它会记住第一次添加项目的顺序,键可以是任何类型,而不仅仅是字符串。Map是比数组数组更有用的结果。但是,如果确实需要数组数组,则可以调用array.from(groupedMap.entries())(对于[key,group array]对的数组)或array.from(groupedMap.values()),(对于简单的数组数组)。它非常灵活;通常情况下,您计划使用此地图进行的任何操作都可以作为缩减的一部分直接完成。

作为最后一点的示例,假设我有一个对象数组,我想按id对其进行(浅)合并,如下所示:

const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}];
// The following variable should be created automatically
const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}]

要做到这一点,我通常首先按id分组,然后合并每个结果数组。相反,您可以直接在reduce()中执行合并:

const mergedArray = Array.from(
    objsToMerge.reduce(
        (entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
        new Map()
    ).values()
);

稍后编辑:

对于大多数目的来说,上述方法可能足够有效。但最初的问题是“最有效”的,正如一些人所指出的,上述解决方案并非如此。问题主要是为每个条目实例化一个新数组。我本以为JS解释器会优化这一点,但似乎并非如此。

有人建议进行编辑来解决这个问题,但看起来确实更复杂。原始代码段已经有点提高了可读性。如果你真的想这样做,请使用for循环!这不是罪!它需要一到两行代码,但它比函数技术更简单,尽管它并不短:

groupedMap = new Map();
for (const e of initialArray) {
    if (groupedMap.has(e.id)) {
        groupedMap.get(e.id).push(e)
    }
    else {
        groupedMap.set(e.id, [e])
    }
}

其他回答

_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}

发件人:http://underscorejs.org/#groupBy

现在有点晚了,但也许有人喜欢这个。

ES6:

常量用户=[{name:“Jim”,颜色:“蓝色”},{name:“Sam”,颜色:“蓝色”},{name:“Eddie”,颜色:“绿色”},{姓名:“Robert”,颜色:“绿色”},];常量groupBy=(arr,key)=>{const initialValue={};返回arr.reduce((acc,cval)=>{const myAttribute=cval[key];acc[myAttribute]=[…(acc[myAattribute]| |[]),cval]返回acc;},initialValue);};const res=groupBy(用户,“颜色”);console.log(“分组依据:”,res);

我会检查一下lodash组,它似乎正是你想要的。它也很轻,非常简单。

Fiddle示例:https://jsfiddle.net/r7szvt5k/

如果数组名称为arr,则带有lodash的groupBy仅为:

import groupBy from 'lodash/groupBy';
// if you still use require:
// const groupBy = require('lodash/groupBy');

const a = groupBy(arr, function(n) {
  return n.Phase;
});
// a is your array grouped by Phase attribute

/***数组分组依据*@类别数组*@function arrayGroupBy*@return{object}{“fieldName”:〔{…}〕,…}*@静态*@作者hht*@param{string}}密钥组密钥*@param{array}数据数组**@示例01* --------------------------------------------------------------------------*从“@xx/utils”导入{arrayGroupBy};*常量数组=[* {*type:'资产',*name:'zhangsan',*年龄:33岁,* },* {*类型:'config',*name:“a”,*年龄:13岁,* },* {*类型:'run',*名称:'lisi',*年龄:“3”,* },* {*类型:'xx',*name:'timo',*年龄:'4',* },*];*arrayGroupBy(array,'type',);**结果:{*资产:[{年龄:'33',名称:'zhangsan',类型:'assets'}],*config:[{age:“13”,名称:“a”,类型:“config”}],*运行:[{age:“3”,名称:“lisi”,类型:“run”}],*xx:[{age:“4”,名称:“timo”,类型:“xx”}],* };**@example示例02 null* --------------------------------------------------------------------------*常量数组=空;*arrayGroupBy(数组,“类型”);**结果:{}**@example示例03键取消绑定* --------------------------------------------------------------------------*常量数组=[* {*type:'资产',*name:'zhangsan',*年龄:33岁,* },* {*类型:'config',*name:“a”,*年龄:13岁,* },* {*类型:'run',*名称:'lisi',*年龄:“3”,* },* {*类型:'xx',*name:'timo',*年龄:'4',* },*];*arrayGroupBy(数组,“xx”);** {}**/const arrayGroupBy=(data,key)=>{if(!data||!Array.isArray(data))返回{};常量groupObj={};data.forEach((项)=>{if(!item[key])返回;const fieldName=项[key];if(!groupObj[fieldName]){groupObj[fieldName]=[item];回来}groupObj[fieldName].push(项);});返回groupObj;};常量数组=[{type:'资产',name:'zhangsan',年龄:33岁,},{类型:'config',name:“a”,年龄:13岁,},{类型:'run',名称:'lisi',年龄:“3”,},{类型:'run',名称:“wangmazi”,年龄:“3”,},{类型:'xx',name:'timo',年龄:'4',},];console.dir(arrayGroupBy(array,'type'))<p>description('arrayGroupBy match',()=>{常量数组=[{type:'资产',name:'zhangsan',年龄:33岁,},{类型:'config',name:“a”,年龄:13岁,},{类型:'run',名称:'lisi',年龄:“3”,},{类型:'xx',name:'timo',年龄:'4',},];测试('arrayGroupBy…',()=>{常量结果={资产:[{年龄:'33',名称:'zhangsan',类型:'assets'}],config:[{age:“13”,名称:“a”,类型:“config”}],运行:[{age:“3”,名称:“lisi”,类型:“run”}],xx:[{age:“4”,名称:“timo”,类型:“xx”}],};expect(arrayGroupBy(array,'type')).toEqual(result);});test('arrayGroupBy不匹配..',()=>{//结果expect(arrayGroupBy(array,'xx')).toEqual({});});test('arrayGroupBy null',()=>{let数组=空;expect(arrayGroupBy(array,'type')).toEqual({});});test('arrayGroupBy undefined',()=>{let array=未定义;expect(arrayGroupBy(array,'type')).toEqual({});});test('arrayGroupBy空',()=>{let数组=[];expect(arrayGroupBy(array,'type')).toEqual({});});});</p>

基于@Ceasar Bautista的原始想法,我修改了代码并使用typescript创建了一个groupBy函数。

static groupBy(data: any[], comparator: (v1: any, v2: any) => boolean, onDublicate: (uniqueRow: any, dublicateRow: any) => void) {
    return data.reduce(function (reducedRows, currentlyReducedRow) {
      let processedRow = reducedRows.find(searchedRow => comparator(searchedRow, currentlyReducedRow));

      if (processedRow) {
        // currentlyReducedRow is a dublicateRow when processedRow is not null.
        onDublicate(processedRow, currentlyReducedRow)
      } else {
        // currentlyReducedRow is unique and must be pushed in the reducedRows collection.
        reducedRows.push(currentlyReducedRow);
      }

      return reducedRows;
    }, []);
  };

此函数接受一个回调(比较器)和一个第二个回调(onDuplicate),该回调比较行并查找副本。

用法示例:

data = [
    { name: 'a', value: 10 },
    { name: 'a', value: 11 },
    { name: 'a', value: 12 },
    { name: 'b', value: 20 },
    { name: 'b', value: 1 }
  ]

  private static demoComparator = (v1: any, v2: any) => {
    return v1['name'] === v2['name'];
  }

  private static demoOnDublicate = (uniqueRow, dublicateRow) => {
    uniqueRow['value'] += dublicateRow['value'];    
  };

使命感

groupBy(data, demoComparator, demoOnDublicate) 

将执行计算值和的分组。

{name: "a", value: 33}
{name: "b", value: 21}

我们可以根据项目的需要创建任意多个回调函数,并根据需要聚合这些值。在一个例子中,我需要合并两个数组,而不是求和数据。