使用 Lodash 将 JavaScript 数组分块

105

我需要把一个JavaScript数组分割成n个大小相同的块。

例如:给定这个数组

["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"]

如果 n 等于 4,输出应该是这样的:

[ ["a1", "a2", "a3", "a4"],
  ["a5", "a6", "a7", "a8"],
  ["a9", "a10", "a11", "a12"],
  ["a13"]
]

我知道纯JavaScript有解决此问题的方法,但由于我已经在使用Lodash,所以我想知道Lodash是否提供了更好的解决方案。

编辑:

我创建了一个jsPerf测试来检查underscore方案有多慢。

5个回答

167

看看lodash的chunkhttps://lodash.com/docs#chunk

const data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
const chunks = _.chunk(data, 3);
console.log(chunks);
// [
//  ["a1", "a2", "a3"],
//  ["a4", "a5", "a6"],
//  ["a7", "a8", "a9"],
//  ["a10", "a11", "a12"],
//  ["a13"]
// ]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>


如果lodash和underscore都具有向后兼容的API,那将会非常有帮助。 - Jonno_FTW
1
@Jonah 没错。之前的答案已经不再是最好的了,因此我为了读者的利益更新了被接受的答案 :) - Cesar Canassa

91

如果想使用 Underscore 实现,请尝试以下解决方案:

var data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
var n = 3;
var lists = _.groupBy(data, function(element, index){
  return Math.floor(index/n);
});
lists = _.toArray(lists); //Added this to convert the returned object to an array.
console.log(lists);
使用链式包装方法,您可以将两个语句组合如下:
var data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
var n = 3;
var lists = _.chain(data).groupBy(function(element, index){
  return Math.floor(index/n);
}).toArray()
.value();

1
奇怪的是,Underscore的groupBy返回一个对象,而不是一个数组。这并不是什么大问题——你基本上可以得到相同的功能——但这是一种奇怪的语义。编辑:哦,它这样做是为了保持链式调用。仍然很有趣。 - Jordan Running
@Cesar Canassa:为了日后阅读那段代码的开发者,请考虑将其作为一个自定义库中的函数,如果您会经常使用它,或者如果您不会,请注释得很好:) - Briguy37
@Jordan:没意识到。编辑了答案将对象转换为数组。 - Chandu
虽然我完全赞成使用Underscore来做它最擅长的事情(而且OP要求基于Underscore的解决方案,所以√是应该的),也支持函数式的解决方案,但这个解决方案似乎引入了大量的开销。正如@ajax33321指出的那样,在循环中使用splice仍然需要三行代码,并且我猜性能更好。如果你的数组很短,可能没关系,但如果你处理数百或数千个项目,请注意它的性能。 - Jordan Running
@Jordan 当我遇到这个问题时,我已经在链接各种下划线函数了。停止链式调用并运行一个splice循环对我来说不是很好看,这就是为什么我选择了underscore解决方案。 - Cesar Canassa
1
这不再是最佳解决方案。请改用chunk()函数。 - Jonah

7

从1.9.0版本开始,Underscore原生支持_.chunk()方法。

const data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
const chunks = _.chunk(data, 4);
console.log(chunks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>


6
可能更简单的表达方式:

const coll = [ "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9" ];
const n = 2;
const chunks = _.range(coll.length / n).map(i => coll.slice(i * n, (i + 1) * n));
console.log(chunks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>


2
比什么更简单?答案应该独立存在。 - Nathan Tuggy

1

试试这个,它更实用(例如,如果您想根据每个子数组中包含的项数拆分数组):

function chunk(arr, start, amount){
    var result = [], 
        i, 
        start = start || 0, 
        amount = amount || 500, 
        len = arr.length;

    do {
        //console.log('appending ', start, '-', start + amount, 'of ', len, '.');
        result.push(arr.slice(start, start+amount));
        start += amount;

    } while (start< len);

    return result;
};

在您的情况下使用的内容:
var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
    chunked = chunk(arr, 0, Math.floor(arr.length/3)); //to get 4 nested arrays

console.log(chunked);

另一个案例:
var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
    chunked = chunk(arr, 0, 3); // to get 6 nested arrays each containing maximum of 3 items

console.log(chunked);

那个 start 参数在我看来并不实用。通常情况下,您不需要传递它;但您总是需要指定块大小。最好交换这两个参数。 - Bergi
为什么使用 do while 循环? 没有人想要获取空块。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接