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

例如,给定此对象数组:

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


当前回答

使用lodash库很简单

let temp = []
  _.map(yourCollectionData, (row) => {
    let index = _.findIndex(temp, { 'Phase': row.Phase })
    if (index > -1) {
      temp[index].Value += row.Value 
    } else {
      temp.push(row)
    }
  })

其他回答

下面的函数允许对任意字段进行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,[“阶段”,“步骤”,“任务”]));

正确答案——只是浅分组。理解减少是很好的。问题还提供了额外总计计算的问题。

这里是一个由一些字段组成的对象数组的REAL GROUP BY,该字段具有1)计算的键名称和2)通过提供所需键的列表来实现分组级联的完整解决方案并将其唯一值转换为根键,如SQL GROUP BY做

常量inputArray=[{阶段:“阶段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”}];var outObject=inputArray.reduce(函数(a,e){//GROUP BY估计密钥(estKey)可能只是一个普通密钥//a——累加器结果对象//e——依次检查的元素,即在此位置测试的元素//可以计算新的分组名称,但必须基于实字段的实际值设estKey=(e['Phase']);(a[estKey]?a[estKey]:(a[est Key]=null | |[])).push(e);返回a;}, {});console.log(outObject);

使用estKey--您可以按多个字段分组,添加其他聚合、计算或其他处理。

您还可以递归地分组数据。例如,最初按阶段分组,然后按步骤字段分组等等脂肪休息数据。

常量输入数组=[{阶段:“阶段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”}];/***获取obj WITH属性的SHALLOW副本的小助手*/const rmProp=(obj,prop)=>(({[prop]:_,…rest})=>rest)(obj))/***按关键字分组数组。结果数组的根键是value*指定密钥的。**@param{Array}src源数组*@param{String}key分组依据的by key*@return{Object}将分组对象作为值的对象*/const grpBy=(src,key)=>src.reduce((a,e)=>((a[e[key]]=a[e[键]]| |[]).push(rmProp(e,键)),a), {});/***如果对象数组仅由具有单个值的对象组成,则折叠该数组。*将其替换为剩余值。*/const blowObj=obj=>Array.isArray(obj)&&obj.length==1&&Object.values(obj[0]).length==1?对象.值(obj[0])[0]:obj;/***带有键列表的递归分组`keyList`可以是数组*或UNIQUE值将使用逗号分隔的键名列表*成为结果对象的关键点。*/const grpByReal=函数(src,keyList){const[key,…rest]=Array.isArray(keyList)?keyList:String(keyList).trim().split(/\s*,\s*/);常量res=键?grpBy(src,key):[…src];if(剩余长度){for(常量k,单位:res){res[k]=grpByReal(res[k],其余)}}其他{for(常量k,单位:res){res[k]=blowObj(res[k])}}回报率;}console.log(JSON.stringify(grpByReal(inputArray,'阶段,步骤,任务'),null,2));

MDN的Array.reduce()文档中有此示例。

// Grouping objects by a property
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

为了补充Scott Sauyet的答案,一些人在评论中询问如何使用他的函数按值1、值2等分组,而不是仅对一个值分组。

只需编辑他的求和函数:

DataGrouper.register("sum", function(item) {
    return _.extend({}, item.key,
        {VALUE1: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE1);}, 0)},
        {VALUE2: _.reduce(item.vals, function(memo, node) {
        return memo + Number(node.VALUE2);}, 0)}
    );
});

保持主组(DataGrouper)不变:

var DataGrouper = (function() {
    var has = function(obj, target) {
        return _.any(obj, function(value) {
            return _.isEqual(value, target);
        });
    };

    var keys = function(data, names) {
        return _.reduce(data, function(memo, item) {
            var key = _.pick(item, names);
            if (!has(memo, key)) {
                memo.push(key);
            }
            return memo;
        }, []);
    };

    var group = function(data, names) {
        var stems = keys(data, names);
        return _.map(stems, function(stem) {
            return {
                key: stem,
                vals:_.map(_.where(data, stem), function(item) {
                    return _.omit(item, names);
                })
            };
        });
    };

    group.register = function(name, converter) {
        return group[name] = function(data, names) {
            return _.map(group(data, names), converter);
        };
    };

    return group;
}());

一种新的方法,有一个用于分组的对象和另外两个用于创建键的函数,并获得一个包含所需分组项的对象和另一个用于添加值的键。

常量groupBy=(数组,组,valueKey)=>{常量getKey=o=>groups.map(k=>o[k]).join('|'),getObject=o=>Object.fromEntries([…groups.map(k=>[k,o[k]]),[valueKey,0]);groups=[].contat(组);return Object.values(array.reduce((r,o)=>{(r[getKey(o)]??=getObject(o))[valueKey]+=+o[valueKey];返回r;}, {}));},data=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段“阶段1“,步骤:”步骤1“,任务:”任务2“,值:”10“},{阶段:”阶段1“、步骤:”第2“、任务:”第1“、值:”15“}、{阶段”阶段1”、步骤:“第2”、任务:“2”、值:“20”}、{阶段“第2阶段”,步骤“步骤:”第一步“,任务“任务:”,值“25”}阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:”35“},{阶段:”阶段2“,步骤:”步骤2“,任务:”任务2“,值::”40“}];console.log(groupBy(data,'Phase','Value'));console.log(groupBy(数据,['Phase','Step'],'Value'));.作为控制台包装{最大高度:100%!重要;顶部:0;}

旧方法:

虽然这个问题有一些答案,而且答案看起来有点过于复杂,但我建议使用香草Javascript进行分组,并使用嵌套的(如果需要)Map。

函数groupBy(数组,组,valueKey){var map=新地图;groups=[].contat(组);返回数组.reduce((r,o)=>{groups.reduce((m,k,i,{length})=>{var子;如果(m.has(o[k]))返回m.get(o[k]);如果(i+1==长度){child=对象.assign(…groups.map(k=>({[k]:o[k]})),{[valueKey]:0});r.push(儿童);}其他{child=新地图;}m.set(o[k],子);回归儿童;},map)[valueKey]+=+o[valueKey];返回r;}, [])};var data=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段“阶段1“,步骤:”步骤1“,任务:”任务2“,值:”10“},{阶段:”阶段1“、步骤:”阶段2“,任务1“,值“15”}、{阶段”阶段1”、步骤:“阶段2”,任务2“、值:”20“}、{阶段2“、步骤1”、任务1“、任务1”、值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:”35“},{阶段:”阶段2“,步骤:”步骤2“,任务:”任务2“,值::”40“}];console.log(groupBy(data,'Phase','Value'));console.log(groupBy(数据,['Phase','Step'],'Value'));.作为控制台包装{最大高度:100%!重要;顶部:0;}