按数组中的对象分组最有效的方法是什么?
例如,给定此对象数组:
[
{ 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,然后遍历生成的对象,自己计算总数?
发帖是因为即使这个问题已经7年了,我仍然没有看到一个符合原始标准的答案:
我不希望它们“拆分”,而是“合并”,更像SQL组方法
我最初发表这篇文章是因为我想找到一种方法来减少对象数组(例如,当您从csv中读取时创建的数据结构),并通过给定索引聚合以生成相同的数据结构。我正在寻找的返回值是另一个对象数组,而不是我在这里看到的嵌套对象或映射。
下面的函数获取一个数据集(对象数组)、一个索引列表(数组)和一个reducer函数,并将reducer功能应用于索引的结果作为一个对象数组返回。
function agg(data, indices, reducer) {
// helper to create unique index as an array
function getUniqueIndexHash(row, indices) {
return indices.reduce((acc, curr) => acc + row[curr], "");
}
// reduce data to single object, whose values will be each of the new rows
// structure is an object whose values are arrays
// [{}] -> {{}}
// no operation performed, simply grouping
let groupedObj = data.reduce((acc, curr) => {
let currIndex = getUniqueIndexHash(curr, indices);
// if key does not exist, create array with current row
if (!Object.keys(acc).includes(currIndex)) {
acc = {...acc, [currIndex]: [curr]}
// otherwise, extend the array at currIndex
} else {
acc = {...acc, [currIndex]: acc[currIndex].concat(curr)};
}
return acc;
}, {})
// reduce the array into a single object by applying the reducer
let reduced = Object.values(groupedObj).map(arr => {
// for each sub-array, reduce into single object using the reducer function
let reduceValues = arr.reduce(reducer, {});
// reducer returns simply the aggregates - add in the indices here
// each of the objects in "arr" has the same indices, so we take the first
let indexObj = indices.reduce((acc, curr) => {
acc = {...acc, [curr]: arr[0][curr]};
return acc;
}, {});
reduceValues = {...indexObj, ...reduceValues};
return reduceValues;
});
return reduced;
}
我将创建一个返回count(*)和sum(Value)的reducer:
reducer = (acc, curr) => {
acc.count = 1 + (acc.count || 0);
acc.value = +curr.Value + (acc.value|| 0);
return acc;
}
最后,使用我们的reducer将agg函数应用于原始数据集会生成一个应用了适当聚合的对象数组:
agg(tasks, ["Phase"], reducer);
// yields:
Array(2) [
0: Object {Phase: "Phase 1", count: 4, value: 50}
1: Object {Phase: "Phase 2", count: 4, value: 130}
]
agg(tasks, ["Phase", "Step"], reducer);
// yields:
Array(4) [
0: Object {Phase: "Phase 1", Step: "Step 1", count: 2, value: 15}
1: Object {Phase: "Phase 1", Step: "Step 2", count: 2, value: 35}
2: Object {Phase: "Phase 2", Step: "Step 1", count: 2, value: 55}
3: Object {Phase: "Phase 2", Step: "Step 2", count: 2, value: 75}
]
此解决方案采用任意函数(而不是键),因此比上述解决方案更灵活,并允许箭头函数,这与LINQ中使用的lambda表达式类似:
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
注意:是否要扩展Array的原型取决于您。
大多数浏览器支持的示例:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
使用箭头函数(ES6)的示例:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
以上两个示例都返回:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
从@mortb、@jmarceli的回答和这篇文章中,
我利用JSON.stringify()作为分组依据的PRIMITIVE VALUE多列的标识。
无第三方
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
if (!map.has(key)) {
map.set(key, [item]);
} else {
map.get(key).push(item);
}
});
return map;
}
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));
console.log(grouped);
使用Lodash第三方
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
let rslt = _.groupBy(pets, pet => JSON.stringify(
{ type: pet.type, age: pet.age }));
console.log(rslt);