javascript - 按键值递归分组(组中组)
我想按多个键进行分组,以便像这样的调用:
groupBy(winners, ['country', 'athlete'])
关于以下数据:
[
{ athlete: "Michael Phelps", age: 19, country: "United States" },
{ athlete: "Michael Phelps", age: 27, country: "United States" },
{ athlete: "Kirsty Coventry", age: 24, country: "Zimbabwe" },
{ athlete: "Allison Schmitt", age: 22, country: "United States" },
]
将产生(按键的顺序嵌套):
{
'United States': {
'Michael Phelps': [
{ athlete: "Michael Phelps", age: 19, country: "United States" },
{ athlete: "Michael Phelps", age: 27, country: "United States" }
],
'Allison Schmitt': [
{ athlete: "Allison Schmitt", age: 22 country: "United States" }
]
},
'Zimbabwe': {
'Kirsty Coventry': [
{ athlete: "Kirsty Coventry", age: 24, country: "Zimbabwe" }
]
}
}
按一个键分组很容易,但我卡在如何让它递归地将每个分组按下一个键继续分组上。这只是把每个键都分组在同一层级:
const get = (obj: Record<string, any>, k: string) =>
k.split(".").reduce((o, i) => (o ? o[i] : o), obj);
type GetValue<Item> = (item: Item) => string;
function groupBy<Item>(items: Item[], keys: (string | GetValue<Item>)[]) {
return keys.reduce(
(acc, key) => {
return items.reduce((accc, item) => {
const value =
typeof key === "function" ? key(item) : get(item, key);
(accc[value] = accc[value] || []).push(item);
return accc;
}, acc);
},
{} as Record<string, any>
);
}
const r = groupBy<Athlete>(winners.slice(0, 50), [
athlete => athlete.country,
"athlete"
]);
这是一个可运行的示例:https://codesandbox.io/s/groupby-ebgly?file=/src/index.ts:244-929
抱歉,额外的复杂性在于使其更容易通过点符号或函数来指定键,以处理更复杂的情况,例如数组中的某个值。
谢谢
答案#1
你可以缩减数组并减少键,然后为最后一个键取一个数组来推入对象。
const
groupBy = (array, keys) => array.reduce((r, o) => {
keys
.reduce((q, k, i, { length }) => q[o[k]] = q[o[k]] || (i + 1 === length ? [] : {}), r)
.push(o);
return r;
}, {}),
winners = [{ athlete: "Michael Phelps", age: 19, country: "United States" }, { athlete: "Michael Phelps", age: 27, country: "United States" }, { athlete: "Kirsty Coventry", age: 24, country: "Zimbabwe" }, { athlete: "Allison Schmitt", age: 22, country: "United States" }];
console.log(groupBy(winners, ['country', 'athlete']));.as-console-wrapper { max-height: 100% !important; top: 0; }答案#2
你可以通过在每一步减少键来对 groupBy 进行递归。
像这样的东西:
const winners = [{
athlete: "Michael Phelps",
age: 19,
country: "United States"
},
{
athlete: "Michael Phelps",
age: 27,
country: "United States"
},
{
athlete: "Kirsty Coventry",
age: 24,
country: "Zimbabwe"
},
{
athlete: "Allison Schmitt",
age: 22,
country: "United States"
},
]
const get = (obj, k) => k.split(".").reduce((o, i) => (o ? o[i] : o), obj);
function groupBy(items, keys) {
const key = keys[0]
const res = items.reduce((accc, item) => {
const k =
typeof key === "function" ? key(item) : get(item, key);
if (typeof k === "string") {
(accc[k] = accc[k] || []).push(item);
}
return accc;
}, {});
if (keys.length - 1 > 0)
return Object.fromEntries(Object.entries(res).map(([key, val]) => [key, groupBy(val, keys.slice(1))]))
else
return res // recursion base
}
const r = groupBy(winners, ['country', 'athlete'])
console.log(r)
