## 1\. 数据和参数和期望结果 ```javascript 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 主要用于异步流处理,所以需要通过回调函数来使用结果。 ```javascript const Rx = require("rxjs"); const out = Rx.Observable.from(data) .bufferCount(groupByNum) .toArray() .do(result => console.log(result)) .subscribe(); ``` ## 3\. \_.chunk() 实现 lodash 提供了大量数据处理的方法,`_.chunk()` 专为分组而生 ```javascript const _ = require("lodash"); const result = _.chunk(data, groupByNum); ``` ### 3.1. \_.chunk() 的源码 在 node 环境也,通过 npm 安装 lodash 之后就能在 `node_modules/lodash` 目录下找到源码文件 `chunk.js` ```javascript npm install lodash ``` 所以完整的源码不贴了,只看下关键的那一句 ```javascript 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` 整除的时候) ```javascript 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` 来实现,虽然不是纯函数式 ```javascript 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 加的组,所以要反个向 } ``` 去掉注释之后的代码还是比较清晰 ```javascript 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` 实现就会更容易理解一些。 ```javascript 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()` 的源码,让我产生了函数式写法的灵感,相比上面的解决方案,更难于理解,不过语法看起来很酷 ```javascript 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 }` 是为了生成一个指定长度的空数组。这里有两个问题要注意: 1. `Array(n)` 可以生成长度为 `n` 的空数组,但是里面的元素是虚填的,调用其 `.map()` 或者 `.forEach()` 结果会出乎意料。可以实验一下: ```auto // 期望输出 10 个 hello,实际并没有 Array(10).forEach(_ => console.log("hello")); ``` 2. ES2015 以后可以使用 `Array.from(Array(n))` 来生成空数组,而不再需要使用之前那个复杂的方法 所以上面的代码可以改为: ```javascript const n = groupByNum; // 偷懒少写几个字,顺便缩减下面代码的长度 const result = Array .from(Array(Math.ceil(data.length / n))) .map((_, i) => data.slice(i * n, (i + 1) * n)); ``` 转至[边城@segmentfault](https://segmentfault.com/a/1190000007464770 "边城@segmentfault.com") Last modification:April 5, 2022 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 0 If you think my article is useful to you, please feel free to appreciate
2 comments
这篇文章如同一首动人的乐章,触动了读者内心深处的柔软。
若能弱化说教语气,传播效果会更好。