一、前言
数据处理与分析中,对数据进行分组是非常常见的功能,不论是实际工作中,还是在面试的场景中应用十分广泛。尤其在函数式编程中 groupBy 十分常见。在 JavaScript 中 groupBy 也已经进入了 JS 的标准。
二、介绍
groupBy
函数是一个用于对 数组元素
进行 分组
的实用函数。groupBy
允许我们指定分组条件,将数组的元素分为不同的组,用于数据处理或者展示。
三、标准
proposal-array-grouping : tc39.es/proposal-ar...
- Object.groupBy
ts
Object.groupBy(items, callbackFn) // 语法,第一个参数是可迭代对象,第二参数是回调函数
- Map.groupBy
ts
Map.groupBy(items, callbackFn) // 语法,第一个参数是可迭代对象,第二参数是回调函数
四、环境支持
五、Object.groupBy 示例
ts
const data = [
{ id: 1, type: 'A' },
{ id: 2, type: 'B' },
{ id: 3, type: 'A' },
{ id: 4, type: 'C' },
{ id: 5, type: 'B' },
];
const groupType = Object.groupBy(data, 'type') // ❌ 第二个参数必须是函数
const groupType = Object.groupBy(data, (item) => item.type) // ✅ 按照类型进行分类, 并且 type 就是 group 的 key 值。
// {
// A: [{...}, {...}]
// B: [{...}, {...}]
// C: [{...}]
/ }
六、实现一个按照给 key 据分组
虽然函数通用性更加广泛,但是复杂度比直接传递数据要稍微高一点,这里实现一个按照传递属性方式实现一个 groupBy:
ts
function groupBy(arr, key) {
return arr.reduce((acc, obj) => {
const groupKey = obj[key];
acc[groupKey] = acc[groupKey] || [];
acc[groupKey].push(obj);
return acc;
}, {});
}
// data 复用以上的 data
groupBy(data, 'type') // 输出结果与 Object.groupBy 一致
实现的核心思想就是 reduce
进行 累加
。从 {}
对象开始,然后再对象上赋值对应key为空数组,符合这个key 的就进入此分组。
七、实现一个通用的 groupBy
有了基于 key 实现 groupBy 之后,实现通用的 groupBy 就比较容易了:
ts
type KeyFunc<T> = (obj: T) => string
export function groupByWithFn<T>(
array: T[],
keyFunc: KeyFunc<T>
): Record<string, T[]> {
return array.reduce((acc: Record<string, T[]>, obj: T) => {
const key: string = keyFunc(obj)
if (!acc[key]) {
acc[key] = []
}
acc[key].push(obj)
return acc
}, {})
}
这里用 TS 实现主要约束类型,本质也非常简单就是 keyFunc
用当前的 obj
执行获取 key,如果没有获取到了,设置一个空的数组初始化,并将数据填充进去。重复累积这个过程。
八、lodash z中的 groupBy
ts
_.groupBy(data, (item) => item.type)
lodash 将 groupBy 放在 collection 集合中,而非数组中。实用方式与以上基本一致。
九、rxjs groupBy
如果是函数式编程,当然离不开 RxJS 中数据操作。rxjs groupBy 不能单独的实现 js 中 groupBy 的功能,当然我们组合操作符或者自定义操作符实现。以下是实现一个自定义操作符 customGroupBy
:
ts
import { Observable, of } from 'rxjs';
import { map, mergeMap, reduce } from 'rxjs/operators';
// 自定义 groupBy 操作符
function customGroupBy(keySelector) {
return function (source) {
return new Observable((observer) => {
source.pipe(
mergeMap((arr) => arr),
reduce((acc, val) => {
const key = keySelector(val);
acc[key] = acc[key] || [];
acc[key].push(val);
return acc;
}, {})
).subscribe({
next: (result) => {
observer.next(result);
observer.complete();
},
error: (err) => observer.error(err),
});
});
};
}
of([
{ id: 1, type: 'A' },
{ id: 2, type: 'B' },
{ id: 3, type: 'A' },
{ id: 4, type: 'C' },
{ id: 5, type: 'B' },
]).pipe(
customGroupBy((obj) => obj.type)
).subscribe({
next: (v) => {console.log(v)}
});
customGroupBy 操作符,接受一个函数作为参数,当然这个函数式是一个闭包,包含了 of 操作符中的静态数据。customGroupBy 是典型的自定义操作符实现,source 的就是 of 制作的静态可观察对象。后面的逻辑也是实用 reduce 进行累加计算。然后对外输出数据
十、groupBy 其他
- 在 SQL 中,GROUP BY 语句用于将行分组为汇总行。
- 在 Python 中,可以使用
itertools.groupby
对可迭代对象进行分组。 - 在一些数据处理例如 excel 以及 python 的工具库中。
十一、小结
本文主要介绍 JavaScript 中的 groupBy 函数实用特性。从 ES 标准草案,到自己实现一个简单的 groupBy, 再到 lodash 以及 RxJS 中实现 groupBy 效果。在函数式编程中,groupBy 实用特性非常高,在数据处理领域广泛适用。