这不是一个小问题。您首先必须定义 什么 构成一个分组,并且您还必须定义 如何 类似的术语将被组合。您的问题因需要按非原始值进行分组而加剧:城市和州。即,我们不能仅根据city 进行分组; more than half the states in the US 有一个城市叫奥克兰。其他答案通过在字符串中序列化城市和州来解决这个问题,但我将向您展示一个更通用的解决方案,适用于任何类型的复合数据。
这是用函数式编程标记的,所以我将从一个用于分离我们的子任务的模块开始
const DeepMap =
{ has: (map, [ k, ...ks ]) =>
ks.length === 0
? map.has (k)
: map.has (k)
? DeepMap.has (map.get (k), ks)
: false
, set: (map, [ k, ...ks ], value) =>
ks.length === 0
? map.set (k, value)
: map.has (k)
? (DeepMap.set (map.get (k), ks, value), map)
: map.set (k, DeepMap.set (new Map, ks, value))
, get: (map, [ k, ...ks ]) =>
ks.length === 0
? map.get (k)
: map.has (k)
? DeepMap.get (map.get (k), ks)
: undefined
}
现在我们可以定义我们的通用 groupBy 函数
const identity = x =>
x
const { has, set, get } =
DeepMap
const groupBy = (key = identity, value = identity, xs = []) =>
xs.reduce
((m, x) =>
has (m, key (x))
? set ( m
, key (x)
, [ ...get (m, key (x)), value (x) ]
)
: set ( m
, key (x)
, [ value (x) ]
)
, new Map
)
我们通过指定key 和value 函数来使用groupBy - key 函数指定项目的分组依据,value 函数指定要添加到组中的值
const people =
[ { name: "John", age: 23, city: "Seattle", state: "WA" }
, { name: "Mark", age: 25, city: "Houston", state: "TX" }
, { name: "Luke", age: 26, city: "Seattle", state: "WA" }
, { name: "Paul", age: 28, city: "Portland", state: "OR" }
, { name: "Matt", age: 21, city: "Oakland", state: "CA" }
, { name: "Sam", age: 24, city: "Oakland", state: "CA" }
]
const res =
groupBy ( k => [ k.state, k.city ]
, v => ({ name: v.name, age: v.age })
, people
)
console.log (res.get ('WA'))
// Map { 'Seattle' => [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ] }
console.log (res.get ('WA') .get ('Seattle'))
// [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
我们可以看到这个中间结果是如何有用的。感谢Map,它提供了令人难以置信的高效查找。当然,您会希望以更有意义的方式遍历深度地图。让我们在我们的模块中添加一个entries 过程
const DeepMap =
{ ...
, entries: function* (map, fields = [])
{
const loop = function* (m, path, [ f, ...fields ])
{
if (fields.length === 0)
for (const [ key, value ] of m)
yield [ { ...path, [ f ]: key }, value ]
else
for (const [ key, value ] of m)
yield* loop (value, { ...path, [ f ]: key }, fields)
}
yield* loop (map, {}, fields)
}
}
for (const [ key, value ] of DeepMap.entries (res, [ 'state', 'city' ]))
console.log (key, value)
// { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
// { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
// { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
// { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
现在我们的深度图是可迭代的,我们可以使用Array.from轻松生成您想要的输出
const arranged =
Array.from ( entries (res, [ 'state', 'city' ])
, ([ key, persons ]) => ({ ...key, persons })
)
console.log (arranged)
// [
// {
// city: "Seattle",
// state: "WA",
// persons: [
// { name: "John", age: 23 },
// { name: "Luke", age: 26 }
// ]
// },
// {
// city: "Houston",
// state: "TX",
// persons: [
// { name: "Mark", age: 25 }
// ]
// },
// {
// city: "Portland",
// state: "OR",
// persons : [
// { name: "Paul", age: 28 }
// ]
// },
// {
// city: "Oakland",
// state: "CA",
// persons: [
// { name: "Matt", age: 21 },
// { name: "Sam", age: 24 }
// ]
// }
// ]
程序演示
const DeepMap =
{ has: (map, [ k, ...ks ]) =>
ks.length === 0
? map.has (k)
: map.has (k)
? DeepMap.has (map.get (k), ks)
: false
, set: (map, [ k, ...ks ], value) =>
ks.length === 0
? map.set (k, value)
: map.has (k)
? (DeepMap.set (map.get (k), ks, value), map)
: map.set (k, DeepMap.set (new Map, ks, value))
, get: (map, [ k, ...ks ]) =>
ks.length === 0
? map.get (k)
: map.has (k)
? DeepMap.get (map.get (k), ks)
: undefined
, entries: function* (map, fields = [])
{
const loop = function* (m, path, [ f, ...fields ])
{
if (fields.length === 0)
for (const [ key, value ] of m)
yield [ { ...path, [ f ]: key }, value ]
else
for (const [ key, value ] of m)
yield* loop (value, { ...path, [ f ]: key }, fields)
}
yield* loop (map, {}, fields)
}
}
const identity = x =>
x
const { has, set, get, entries } =
DeepMap
const groupBy = (key = identity, value = identity, xs = []) =>
xs.reduce
((m, x) =>
has (m, key (x))
? set ( m
, key (x)
, [ ...get (m, key (x)), value (x) ]
)
: set ( m
, key (x)
, [ value (x) ]
)
, new Map
)
const people =
[ { name: "John", age: 23, city: "Seattle", state: "WA" }
, { name: "Mark", age: 25, city: "Houston", state: "TX" }
, { name: "Luke", age: 26, city: "Seattle", state: "WA" }
, { name: "Paul", age: 28, city: "Portland", state: "OR" }
, { name: "Matt", age: 21, city: "Oakland", state: "CA" }
, { name: "Sam", age: 24, city: "Oakland", state: "CA" }
]
const res =
groupBy ( k => [ k.state, k.city ]
, v => ({ name: v.name, age: v.age })
, people
)
for (const [ key, value ] of entries (res, [ 'state', 'city' ]))
console.log (key, value)
// { state: 'WA', city: 'Seattle' } [ { name: 'John', age: 23 }, { name: 'Luke', age: 26 } ]
// { state: 'TX', city: 'Houston' } [ { name: 'Mark', age: 25 } ]
// { state: 'OR', city: 'Portland' } [ { name: 'Paul', age: 28 } ]
// { state: 'CA', city: 'Oakland' } [ { name: 'Matt', age: 21 }, { name: 'Sam', age: 24 } ]
const arranged =
Array.from ( entries (res, [ 'state', 'city '])
, ([ key, persons ]) => ({ ...key, persons })
)
console.log ('arranged', arranged)
// arranged [
// {
// city: "Seattle",
// state: "WA",
// persons: [
// { name: "John", age: 23 },
// { name: "Luke", age: 26 }
// ]
// },
// {
// city: "Houston",
// state: "TX",
// persons: [
// { name: "Mark", age: 25 }
// ]
// },
// {
// city: "Portland",
// state: "OR",
// persons : [
// { name: "Paul", age: 28 }
// ]
// },
// {
// city: "Oakland",
// state: "CA",
// persons: [
// { name: "Matt", age: 21 },
// { name: "Sam", age: 24 }
// ]
// }
// ]