按数组中的对象分组最有效的方法是什么?
例如,给定此对象数组:
[
{ 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,然后遍历生成的对象,自己计算总数?
我从underscore.js fiddler那里借用了这个方法
window.helpers=(function (){
var lookupIterator = function(value) {
if (value == null){
return function(value) {
return value;
};
}
if (typeof value === 'function'){
return value;
}
return function(obj) {
return obj[value];
};
},
each = function(obj, iterator, context) {
var breaker = {};
if (obj == null) return obj;
if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = []
for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key)
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj;
},
// An internal function used for aggregate "group by" operations.
group = function(behavior) {
return function(obj, iterator, context) {
var result = {};
iterator = lookupIterator(iterator);
each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
});
return result;
};
};
return {
groupBy : group(function(result, key, value) {
Object.prototype.hasOwnProperty.call(result, key) ? result[key].push(value) : result[key] = [value];
})
};
})();
var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}];
console.dir(helpers.groupBy(arr,"b"));
console.dir(helpers.groupBy(arr,function (el){
return el.b>2;
}));
发帖是因为即使这个问题已经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}
]
为了补充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;
}());
var arr = [
{ Phase: "Phase 1", `enter code here`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" }
];
创建并清空对象。循环遍历arr并添加使用Phase作为obj的唯一键。在循环遍历arr时,保持更新obj中的键总数。
const obj = {};
arr.forEach((item) => {
obj[item.Phase] = obj[item.Phase] ? obj[item.Phase] +
parseInt(item.Value) : parseInt(item.Value);
});
结果如下:
{ "Phase 1": 50, "Phase 2": 130 }
循环通过obj形成表单和resultArr。
const resultArr = [];
for (item in obj) {
resultArr.push({ Phase: item, Value: obj[item] });
}
console.log(resultArr);
这是一个基于TS的功能,不是性能最好的,但很容易阅读和理解!
function groupBy<T>(array: T[], key: string): Record<string, T[]> {
const groupedObject = {}
for (const item of array) {
const value = item[key]
if (groupedObject[value] === undefined) {
groupedObject[value] = []
}
groupedObject[value].push(item)
}
return groupedObject
}
我们以->
const data = [
{ 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" },
];
console.log(groupBy(data, 'Step'))
{
'Step 1': [
{
Phase: 'Phase 1',
Step: 'Step 1',
Task: 'Task 1',
Value: '5'
},
{
Phase: 'Phase 1',
Step: 'Step 1',
Task: 'Task 2',
Value: '10'
}
],
'Step 2': [
{
Phase: 'Phase 1',
Step: 'Step 2',
Task: 'Task 1',
Value: '15'
},
{
Phase: 'Phase 1',
Step: 'Step 2',
Task: 'Task 2',
Value: '20'
}
]
}