按数组中的对象分组最有效的方法是什么?
例如,给定此对象数组:
[
{ 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,然后遍历生成的对象,自己计算总数?
这是一个基于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'
}
]
}
具有排序功能
export const groupBy = function groupByArray(xs, key, sortKey) {
return xs.reduce(function(rv, x) {
let v = key instanceof Function ? key(x) : x[key];
let el = rv.find(r => r && r.key === v);
if (el) {
el.values.push(x);
el.values.sort(function(a, b) {
return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
});
} else {
rv.push({ key: v, values: [x] });
}
return rv;
}, []);
};
示例:
var state = [
{
name: "Arkansas",
population: "2.978M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
category: "city"
},{
name: "Crkansas",
population: "2.978M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
category: "city"
},
{
name: "Balifornia",
population: "39.14M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
category: "city"
},
{
name: "Florida",
population: "20.27M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
category: "airport"
},
{
name: "Texas",
population: "27.47M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
category: "landmark"
}
];
console.log(JSON.stringify(groupBy(state,'category','name')));
这里有一个使用ES6的讨厌的、难以阅读的解决方案:
export default (arr, key) =>
arr.reduce(
(r, v, _, __, k = v[key]) => ((r[k] || (r[k] = [])).push(v), r),
{}
);
对于那些询问这是如何工作的人,这里有一个解释:
在这两个=>中,您可以获得免费回报Array.prototype.reduce函数最多包含4个参数。这就是为什么要添加第五个参数,这样我们就可以使用默认值在参数声明级别为组(k)创建一个廉价的变量声明。(是的,这是巫术)如果我们的当前组在上一次迭代中不存在,我们将创建一个新的空数组((r[k]||(r[k]=[]))。这将计算到最左边的表达式,换句话说,一个现有数组或一个空数组,这就是为什么在该表达式之后会立即推送,因为无论哪种方式都会得到一个数组。当有一个返回时,逗号运算符将丢弃最左边的值,返回该场景中经过调整的前一组。
更容易理解的版本是:
export default (array, key) =>
array.reduce((previous, currentItem) => {
const group = currentItem[key];
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {});
编辑:
TS版本:
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);