按数组中的对象分组最有效的方法是什么?
例如,给定此对象数组:
[
{ 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,然后遍历生成的对象,自己计算总数?
我已经改进了答案。此函数获取组字段数组并返回分组对象,该对象的键也是组字段的对象。
function(xs, groupFields) {
groupFields = [].concat(groupFields);
return xs.reduce(function(rv, x) {
let groupKey = groupFields.reduce((keyObject, field) => {
keyObject[field] = x[field];
return keyObject;
}, {});
(rv[JSON.stringify(groupKey)] = rv[JSON.stringify(groupKey)] || []).push(x);
return rv;
}, {});
}
let x = [
{
"id":1,
"multimedia":false,
"language":["tr"]
},
{
"id":2,
"multimedia":false,
"language":["fr"]
},
{
"id":3,
"multimedia":true,
"language":["tr"]
},
{
"id":4,
"multimedia":false,
"language":[]
},
{
"id":5,
"multimedia":false,
"language":["tr"]
},
{
"id":6,
"multimedia":false,
"language":["tr"]
},
{
"id":7,
"multimedia":false,
"language":["tr","fr"]
}
]
groupBy(x, ['multimedia','language'])
//{
//{"multimedia":false,"language":["tr"]}: Array(3),
//{"multimedia":false,"language":["fr"]}: Array(1),
//{"multimedia":true,"language":["tr"]}: Array(1),
//{"multimedia":false,"language":[]}: Array(1),
//{"multimedia":false,"language":["tr","fr"]}: Array(1)
//}
基于@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}
我们可以根据项目的需要创建任意多个回调函数,并根据需要聚合这些值。在一个例子中,我需要合并两个数组,而不是求和数据。
发帖是因为即使这个问题已经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}
]