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

例如,给定此对象数组:

[ 
    { 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,然后遍历生成的对象,自己计算总数?


当前回答

var newArr = data.reduce((acc, cur) => {
    const existType = acc.find(a => a.Phase === cur.Phase);
    if (existType) {
        existType.Value += +cur.Value;
        return acc;
    }

    acc.push({
        Phase: cur.Phase,
        Value: +cur.Value
    });
    return acc;
}, []);

其他回答

让我们生成一个通用的Array.protocol.groupBy()工具。为了多样化,让我们在递归方法上使用ES6 fancyty扩展运算符进行Haskell式模式匹配。同样,让我们让Array.prototype.groupBy()接受一个回调,该回调将项(e)、索引(i)和应用的数组(a)作为参数。

Array.prototype.groupBy=函数(cb){返回函数迭代([x,…xs],i=0,r=[[],[]]){cb(x,i,[x,…xs])?(r[0].推(x),r):(r[1].推(x),r);是否返回xs.length?迭代(xs,++i,r):r;}(本);};var arr=[0,1,2,3,4,5,6,7,8,9],res=arr.groupBy(e=>e<5);console.log(res);

如果您需要通过以下方式进行多组:


    const populate = (entireObj, keys, item) => {
    let keysClone = [...keys],
        currentKey = keysClone.shift();

    if (keysClone.length > 0) {
        entireObj[item[currentKey]] = entireObj[item[currentKey]] || {}
        populate(entireObj[item[currentKey]], keysClone, item);
    } else {
        (entireObj[item[currentKey]] = entireObj[item[currentKey]] || []).push(item);
    }
}

export const groupBy = (list, key) => {
    return list.reduce(function (rv, x) {

        if (typeof key === 'string') (rv[x[key]] = rv[x[key]] || []).push(x);

        if (typeof key === 'object' && key.length) populate(rv, key, x);

        return rv;

    }, {});
}

const myPets = [
    {name: 'yaya', type: 'cat', color: 'gray'},
    {name: 'bingbang', type: 'cat', color: 'sliver'},
    {name: 'junior-bingbang', type: 'cat', color: 'sliver'},
    {name: 'jindou', type: 'cat', color: 'golden'},
    {name: 'dahuzi', type: 'dog', color: 'brown'},
];

// run 
groupBy(myPets, ['type', 'color']));

// you will get object like: 

const afterGroupBy = {
    "cat": {
        "gray": [
            {
                "name": "yaya",
                "type": "cat",
                "color": "gray"
            }
        ],
        "sliver": [
            {
                "name": "bingbang",
                "type": "cat",
                "color": "sliver"
            },
            {
                "name": "junior-bingbang",
                "type": "cat",
                "color": "sliver"
            }
        ],
        "golden": [
            {
                "name": "jindou",
                "type": "cat",
                "color": "golden"
            }
        ]
    },
    "dog": {
        "brown": [
            {
                "name": "dahuzi",
                "type": "dog",
                "color": "brown"
            }
        ]
    }
};

我想建议一下我的方法。首先,分开分组和聚合。让我们声明原型“groupby”函数。它需要另一个函数为要分组的每个数组元素生成“哈希”字符串。

Array.prototype.groupBy = function(hash){
  var _hash = hash ? hash : function(o){return o;};

  var _map = {};
  var put = function(map, key, value){
    if (!map[_hash(key)]) {
        map[_hash(key)] = {};
        map[_hash(key)].group = [];
        map[_hash(key)].key = key;

    }
    map[_hash(key)].group.push(value); 
  }

  this.map(function(obj){
    put(_map, obj, obj);
  });

  return Object.keys(_map).map(function(key){
    return {key: _map[key].key, group: _map[key].group};
  });
}

分组完成后,您可以根据需要聚合数据

data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
    /* aggreagating */
    .map(function(el){ 
         var sum = el.group.reduce(
           function(l,c){
             return l + parseInt(c.Value);
           },
           0
         );
         el.key.Value = sum; 
         return el.key;
    });

一般来说,它是有效的。我已经在chrome控制台中测试了这段代码。并随时改进和发现错误;)

/***数组分组依据*@类别数组*@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>

下面的函数允许对任意字段进行groupBy(和求和值-OP需要的)。在解决方案中,我们定义cmp函数来根据分组字段比较两个对象。在设w=。。。我们创建子集对象x字段的副本。在y[sumBy]=+y[sumBy]+(+x[sumBy])中,我们使用“+”将字符串转换为数字。

function groupBy(data, fields, sumBy='Value') {
  let r=[], cmp= (x,y) => fields.reduce((a,b)=> a && x[b]==y[b], true);
  data.forEach(x=> {
    let y=r.find(z=>cmp(x,z));
    let w= [...fields,sumBy].reduce((a,b) => (a[b]=x[b],a), {})
    y ? y[sumBy]=+y[sumBy]+(+x[sumBy]) : r.push(w);
  });
  return r;
}

常量d=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务2”,值:“20”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”}];函数groupBy(数据,字段,sumBy='Value'){设r=[],cmp=(x,y)=>fields.reduce((a,b)=>a&&x[b]==y[b],true);data.forEach(x=>{设y=r.find(z=>cmp(x,z));设w=[…fields,sumBy].reduce((a,b)=>(a[b]=x[b],a),{})yy[sumBy]=+y[sumBy]+(+x[sumBy]):r.push(w);});返回r;}//测试let p=(t,o)=>console.log(t,JSON.stringify(o));console.log('GROUP BY:');p(“相”,组By(d,[“相”]));p(“步骤”,组By(d,[“步骤”]));p(“阶段-步骤”,组By(d,[“阶段”,“步骤”]));p(“阶段任务”,groupBy(d,[“阶段”,“任务”]));p(“步骤任务”,groupBy(d,[“步骤”,“任务”]));p(“阶段-步骤-任务”,groupBy(d,[“阶段”,“步骤”,“任务”]));