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

例如,给定此对象数组:

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


当前回答

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 }] 
// }

其他回答

您可以使用Alasql JavaScript库来实现:

var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
             { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }];

var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \
                  FROM ? GROUP BY Phase, Step',[data]);

在jsFiddle尝试这个示例。

BTW:在大型阵列(100000条记录及以上)上,Alasql比Linq更快。参见jsPref中的测试。

评论:

这里我将Value放在方括号中,因为Value是SQL中的关键字我必须使用CAST()函数将字符串值转换为数字类型。

这里有一个ES6版本,不会在空成员上中断

function groupBy (arr, key) {
  return (arr || []).reduce((acc, x = {}) => ({
    ...acc,
    [x[key]]: [...acc[x[key]] || [], x]
  }), {})
}

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

常量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;}

解释相同的代码。喜欢它我喜欢这里

const groupBy = (array, key) => {
  return array.reduce((result, currentValue) => {
    (result[currentValue[key]] = result[currentValue[key]] || []).push(
      currentValue
    );
    console.log(result);
    return result;
  }, {});
};

USE

 let group =   groupBy(persons, 'color');
Array.prototype.groupBy = function(keyFunction) {
    var groups = {};
    this.forEach(function(el) {
        var key = keyFunction(el);
        if (key in groups == false) {
            groups[key] = [];
        }
        groups[key].push(el);
    });
    return Object.keys(groups).map(function(key) {
        return {
            key: key,
            values: groups[key]
        };
    });
};