javascript 数组分组
1. 数据和参数和期望结果
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
const groupByNum = 3;
//return [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ], [ 10, 11 ] ]2. RxJS
RxJava 很火,其实 ReactiveX 有很多种语言的实现,JavaScript 的实例就是 RxJs,建议学习的同学直接上 5.0 Beta。不过 RxJs 主要用于异步流处理,所以需要通过回调函数来使用结果。
const Rx = require("rxjs");
const out = Rx.Observable.from(data)
.bufferCount(groupByNum)
.toArray()
.do(result => console.log(result))
.subscribe();3. \_.chunk() 实现
lodash 提供了大量数据处理的方法,_.chunk() 专为分组而生
const _ = require("lodash");
const result = _.chunk(data, groupByNum);3.1. \_.chunk() 的源码
在 node 环境也,通过 npm 安装 lodash 之后就能在 node_modules/lodash 目录下找到源码文件 chunk.js
npm install lodash所以完整的源码不贴了,只看下关键的那一句
while (index < length) {
result[resIndex++] = baseSlice(array, index, (index += size));
}baseSlice() 是 lodash 对 Array.prototype.slice() 的兼容性实现,可以直接当 slice() 来看。看了这个源码,我有了函数式写法的思路,后面通过 slice() + map() 实现部分详述。
4. reduce() 实现
像这种,目标数组长度和原数组长度不一致的情况,函数式写法很容易想到 reduce() 函数。只可惜单纯的 reduce() 做不出来(在 data.length 不能被 groupByNum 整除的时候)
function groupArray(data, cols) {
const r = data.reduce((r, t) => {
r.current.push(t);
if (r.current.length === cols) {
r.list.push(r.current);
r.current = [];
}
return r;
}, { list: [], current: [] });
if (r.current.length) {
r.list.push(r.current);
}
return r.list;
}
const result = groupArray(data, groupByNum);reduce() 的初始化对象是 { list: [], current: [] },其中 list 是要得计算出来的结果,而 current 是中间变量,用于生成每个组。
最后由于不有保证 data.length 一定被 groupByNum 整除,所以可能会有一个未完成的 current 没被 push 到 list 当中,所以专门进行了一个判断和处理。因此不能写成函数式写法,有些遗憾。
4.1.
现在回想了一下,可以用 reduce 来实现,虽然不是纯函数式
function groupArray(data, cols) {
return data
.reduce(
([groups, subIndex], d) => {
// subIndex 根据 cols 在 [0, cols) 区间循环,可通过取余来更新 subIndex
// 所以,subIndex === 0 表示产生了一个新的分组
if (subIndex === 0) {
// 为了方便访问最后加入的分组,用 unshift 倒插
// 这样后面就可以用 groups[0] 获取最新的分组
groups.unshift([]);
// 当然这里用 push,后面用 groups[groups.length - 1] 也是可以的
}
// 将数据加入到最近的一个分组中
groups[0].push(d);
return [groups, (subIndex + 1) % cols];
},
[[], 0] // 初始值,groups = [], subIndex = 0
)[0] // reduce 的结果是 [groups, subIndex],所以用 [0] 把 groups 取出来
.reverse(); // 前面 unshift 加的组,所以要反个向
}去掉注释之后的代码还是比较清晰
function groupArray(data, cols) {
return data
.reduce(
([groups, subIndex], d) => {
if (subIndex === 0) {
groups.unshift([]);
}
groups[0].push(d);
return [groups, (subIndex + 1) % cols];
},
[[], 0]
)[0]
.reverse();
}5. forEach() 实现
既然不能用函数式写法,那 forEach() 或者 for ... of 实现就会更容易理解一些。
function groupArray(data, cols) {
const list = [];
let current = [];
// for (t of data) {
data.forEach(t => {
current.push(t);
if (current.length === cols) {
list.push(current);
current = [];
}
});
// } // for (t of data)
if (current.length) {
list.push(current);
}
return list;
}6. slice() + map() 实现
看到了 _.chunk() 的源码,让我产生了函数式写法的灵感,相比上面的解决方案,更难于理解,不过语法看起来很酷
const result = Array.apply(null, {
length: Math.ceil(data.length / groupByNum)
}).map((_, i) => {
return data.slice(i * groupByNum, (i + 1) * groupByNum);
});Array.apply() 是为了生成一个长度为 Math.ceil(data.length / groupByNum) 的数组作为 map() 的源,map() 不需要这个源的数据,只需要这个源每个数组的 index。
Math.ceil() 用于保证在除法计算有余数的时候对商 +1,即循环次数 +1。
然后在算得的循环次数中,通过 slice 返回每一段结果,通过 map() 映射出来,最终生成需要的结果。
6.1.
上面的 Array.apply(null, { length: n } 是为了生成一个指定长度的空数组。这里有两个问题要注意:
Array(n)可以生成长度为n的空数组,但是里面的元素是虚填的,调用其.map()或者.forEach()结果会出乎意料。可以实验一下:// 期望输出 10 个 hello,实际并没有 Array(10).forEach(_ => console.log("hello"));- ES2015 以后可以使用
Array.from(Array(n))来生成空数组,而不再需要使用之前那个复杂的方法
所以上面的代码可以改为:
const n = groupByNum; // 偷懒少写几个字,顺便缩减下面代码的长度
const result = Array
.from(Array(Math.ceil(data.length / n)))
.map((_, i) => data.slice(i * n, (i + 1) * n)); 标签:JavaScript, 杂记