将数组拆分为块

假设我有一个Javascript数组,如下所示:

["Element 1","Element 2","Element 3",...]; // with close to a hundred elements.

什么方法适合将数组分块(拆分)成许多较小的数组,例如最多10个元素?

1025152 次浏览

array.slice()方法可以从数组的开头、中间或结尾提取切片,用于您需要的任何目的,而无需更改原始数组。

const chunkSize = 10;for (let i = 0; i < array.length; i += chunkSize) {const chunk = array.slice(i, i + chunkSize);// do whatever}

最后一个chunk可能小于chunkSize。例如,当给定12个元素中的array时,第一个块将有10个元素,第二个块只有2个元素。

请注意,0chunkSize将导致无限循环。

编辑:@mblase75在我编写我的答案时为前面的答案添加了更简洁的代码,所以我建议使用他的解决方案。

你可以使用这样的代码:

var longArray = ["Element 1","Element 2","Element 3", /*...*/];var smallerArrays = []; // will contain the sub-arrays of 10 elements eachvar arraySize = 10;for (var i=0;i<Math.ceil(longArray.length/arraySize);i++) {smallerArrays.push(longArray.slice(i*arraySize,i*arraySize+arraySize));}

更改arraySize的值以更改较小数组的最大长度。

修改自dbaseman的答案:https://stackoverflow.com/a/10456344/711085

Object.defineProperty(Array.prototype, 'chunk_inefficient', {value: function(chunkSize) {var array = this;return [].concat.apply([],array.map(function(elem, i) {return i % chunkSize ? [] : [array.slice(i, i + chunkSize)];}));}});
console.log([1, 2, 3, 4, 5, 6, 7].chunk_inefficient(3))// [[1, 2, 3], [4, 5, 6], [7]]


次要增编

我应该指出,上面是使用Array.map的一个不那么优雅的(在我看来)解决方法。它基本上执行以下操作,其中~是连接:

[[1,2,3]]~[]~[]~[] ~ [[4,5,6]]~[]~[]~[] ~ [[7]]

它与下面的方法具有相同的渐近运行时间,但由于构建空列表,可能是一个更糟糕的常数因子。可以如下重写(与Blazemonger的方法基本相同,这就是为什么我最初没有提交这个答案):

更有效的方法:

// refresh page if experimenting and you already defined Array.prototype.chunk
Object.defineProperty(Array.prototype, 'chunk', {value: function(chunkSize) {var R = [];for (var i = 0; i < this.length; i += chunkSize)R.push(this.slice(i, i + chunkSize));return R;}});
console.log([1, 2, 3, 4, 5, 6, 7].chunk(3))


我现在最喜欢的方式是上面的,或者以下之一:

Array.range = function(n) {// Array.range(5) --> [0,1,2,3,4]return Array.apply(null,Array(n)).map((x,i) => i)};
Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {
// ACTUAL CODE FOR CHUNKING ARRAY:return Array.range(Math.ceil(this.length/n)).map((x,i) => this.slice(i*n,i*n+n));
}});

演示:

> JSON.stringify( Array.range(10).chunk(3) );[[1,2,3],[4,5,6],[7,8,9],[10]]

或者,如果你不想要一个Array.range函数,它实际上只是一个单行代码(不包括绒毛):

var ceil = Math.ceil;
Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {return Array(ceil(this.length/n)).fill().map((_,i) => this.slice(i*n,i*n+n));}});

Object.defineProperty(Array.prototype, 'chunk', {value: function(n) {return Array.from(Array(ceil(this.length/n)), (_,i)=>this.slice(i*n,i*n+n));}});

老问题:新答案!实际上我正在处理这个问题的答案,并且有一个朋友改进了它!所以这里是:

Array.prototype.chunk = function ( n ) {if ( !this.length ) {return [];}return [ this.slice( 0, n ) ].concat( this.slice(n).chunk(n) );};
[1,2,3,4,5,6,7,8,9,0].chunk(3);> [[1,2,3],[4,5,6],[7,8,9],[0]]

如果你不知道谁会使用你的代码(第三方、同事、你自己等),尽量避免使用原生原型,包括Array.prototype

有一些方法可以安全地扩展原型(但不是在所有浏览器中),也有一些方法可以安全地使用从扩展原型创建的对象,但更好的经验法则是遵循最小意外原则并完全避免这些做法。

如果您有时间,请观看Andrew Dupont的JSConf 2011演讲“一切都是允许的:扩展内置”,了解有关此主题的精彩讨论。

但是回到问题,虽然上面的解决方案可行,但它们过于复杂,需要不必要的计算开销。这是我的解决方案:

function chunk (arr, len) {
var chunks = [],i = 0,n = arr.length;
while (i < n) {chunks.push(arr.slice(i, i += len));}
return chunks;}
// Optionally, you can do the following to avoid cluttering the global namespace:Array.chunk = chunk;

我更喜欢使用拼接方法:

var chunks = function(array, size) {var results = [];while (array.length) {results.push(array.splice(0, size));}return results;};

好吧,让我们从一个相当紧的开始:

function chunk(arr, n) {return arr.slice(0,(arr.length+n-1)/n|0).map(function(c,i) { return arr.slice(n*i,n*i+n); });}

它是这样使用的:

chunk([1,2,3,4,5,6,7], 2);

那么我们就有了这个紧密的减速机功能:

function chunker(p, c, i) {(p[i/this|0] = p[i/this|0] || []).push(c);return p;}

它是这样使用的:

[1,2,3,4,5,6,7].reduce(chunker.bind(3),[]);

因为当我们将this绑定到一个数字时,一只小猫就会死亡,我们可以像这样手动柯里化:

// Fluent alternative API without prototype hacks.function chunker(n) {return function(p, c, i) {(p[i/n|0] = p[i/n|0] || []).push(c);return p;};}

它是这样使用的:

[1,2,3,4,5,6,7].reduce(chunker(3),[]);

然后是仍然非常紧凑的函数,它一次完成所有工作:

function chunk(arr, n) {return arr.reduce(function(p, cur, i) {(p[i/n|0] = p[i/n|0] || []).push(cur);return p;},[]);}
chunk([1,2,3,4,5,6,7], 3);
in coffeescript:
b = (a.splice(0, len) while a.length)
demoa = [1, 2, 3, 4, 5, 6, 7]
b = (a.splice(0, 2) while a.length)[ [ 1, 2 ],[ 3, 4 ],[ 5, 6 ],[ 7 ] ]

我将不同的答案测试到jsperf.com.结果在那里可用:https://web.archive.org/web/20150909134228/https://jsperf.com/chunk-mtds

最快的函数(可以从IE8运行)是这个:

function chunk(arr, chunkSize) {if (chunkSize <= 0) throw "Invalid chunk size";var R = [];for (var i=0,len=arr.length; i<len; i+=chunkSize)R.push(arr.slice(i,i+chunkSize));return R;}
# in coffeescript# assume "ar" is the original array# newAr is the new array of arrays
newAr = []chunk = 10for i in [0... ar.length] by chunknewAr.push ar[i... i+chunk]
# or, print out the elements one line per chunkfor i in [0... ar.length] by chunkconsole.log ar[i... i+chunk].join ' '

我稍微改变了BlazeMonger的用于jQuery对象。

var $list = $('li'),$listRows = [];

for (var i = 0, len = $list.length, chunk = 4, n = 0; i < len; i += chunk, n++) {$listRows[n] = $list.slice(i, i + chunk);}

现在,您可以使用Lodash的chunk函数将数组拆分为更小的数组https://lodash.com/docs#chunk不再需要摆弄循环!

我创建了以下jsfiddle来演示我对您的问题的方法。

(function() {// Sample arraysvar //elements = ["0", "1", "2", "3", "4", "5", "6", "7"],elements = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43"];
var splitElements = [],delimiter = 10; // Change this value as needed      
// parameters: array, number of elements to split the array byif(elements.length > delimiter){splitElements = splitArray(elements, delimiter);}else {// No need to do anything if the array's length is less than the delimitersplitElements = elements;}  
//Displaying result in consolefor(element in splitElements){if(splitElements.hasOwnProperty(element)){console.log(element + " | " + splitElements[element]);}}})();
function splitArray(elements, delimiter) {var elements_length = elements.length;
if (elements_length > delimiter) {var myArrays = [], // parent array, used to store each sub arrayfirst = 0, // used to capture the first element in each sub arrayindex = 0; // used to set the index of each sub array
for (var i = 0; i < elements_length; ++i) {if (i % delimiter === 0) {// Capture the first element of each sub array from the original array, when i is a modulus factor of the delimiter.first = i;} else if (delimiter - (i % delimiter) === 1) {// Build each sub array, from the original array, sliced every time the i one minus the modulus factor of the delimiter.index = (i + 1) / delimiter - 1;myArrays[index] = elements.slice(first, i + 1);}else if(i + 1 === elements_length){// Build the last sub array which contain delimiter number or less elementsmyArrays[index + 1] = elements.slice(first, i + 1);}}// Returned is an array of arraysreturn myArrays;}}

首先,我有两个例子:一个元素少于八个的数组,另一个元素多于八个的数组(注释您不想使用的任何一个)。

然后我检查数组的大小,这很简单,但对于避免额外的计算至关重要。从这里开始,如果数组符合条件(数组大小>delimiter),我们进入splitArray函数。

splitArray函数接收分隔符(意思是8,因为这是你想要分割的)和数组本身。由于我们大量重复使用数组长度,我将其缓存在一个变量中,以及firstlast

first表示数组中第一个元素的位置。该数组是由8个元素组成的数组。因此,为了确定第一个元素,我们使用模运算符

myArrays是数组的数组。我们将在每个索引处存储大小为8或更小的任何子数组。这是下面算法中的关键策略。

index代表myArrays变量的索引。每次存储8个元素或更少的子数组时,它都需要存储在相应的索引中。所以如果我们有27个元素,那意味着4个数组。第一、第二和第三个数组每个数组有8个元素。最后一个数组只有3个元素。所以index分别是0、1、2和3。

棘手的部分只是找出数学并尽可能优化它。例如else if (delimiter - (i % delimiter) === 1),这是为了找到数组中应该包含的最后一个元素,当数组已满时(例如:包含10个元素)。

这段代码适用于每一个场景,你甚至可以更改delimiter以匹配你想要的任何数组大小。非常甜蜜:-)

有什么问题吗?欢迎在下面的评论中提问。

为此https://www.npmjs.com/package/array.chunk创建了一个npm包

var result = [];
for (var i = 0; i < arr.length; i += size) {result.push(arr.slice(i, size + i));}return result;

使用类型化数组

var result = [];
for (var i = 0; i < arr.length; i += size) {result.push(arr.subarray(i, size + i));}return result;

我只是在group by函数的帮助下写了这个。

// utilsconst group = (source) => ({by: (grouping) => {const groups = source.reduce((accumulator, item) => {const name = JSON.stringify(grouping(item));accumulator[name] = accumulator[name] || [];accumulator[name].push(item);return accumulator;}, {});
return Object.keys(groups).map(key => groups[key]);}});
const chunk = (source, size) => group(source.map((item, index) => ({ item, index }))).by(x => Math.floor(x.index / size)).map(x => x.map(v => v.item));

// 103 itemsconst arr = [6,2,6,6,0,7,4,9,3,1,9,6,1,2,7,8,3,3,4,6,8,7,6,9,3,6,3,5,0,9,3,7,0,4,1,9,7,5,7,4,3,4,8,9,0,5,1,0,0,8,0,5,8,3,2,5,6,9,0,0,1,5,1,7,0,6,1,6,8,4,9,8,9,1,6,5,4,9,1,6,6,1,8,3,5,5,7,0,8,3,1,7,1,1,7,6,4,9,7,0,5,1,0];
const chunks = chunk(arr, 10);
console.log(JSON.stringify(chunks));

这是我使用Coffeescript列表理解的方法。一篇详细介绍Coffeescript中理解的好文章可以是在这里找到

chunk: (arr, size) ->chunks = (arr.slice(index, index+size) for item, index in arr by size)return chunks

如果您使用EcmaScript版本>=5.1,您可以使用复杂度为O(N)的array.reduce()实现chunk()的函数版本:

function chunk(chunkSize, array) {return array.reduce(function(previous, current) {var chunk;if (previous.length === 0 ||previous[previous.length -1].length === chunkSize) {chunk = [];   // 1previous.push(chunk);   // 2}else {chunk = previous[previous.length -1];   // 3}chunk.push(current);   // 4return previous;   // 5}, []);   // 6}
console.log(chunk(2, ['a', 'b', 'c', 'd', 'e']));// prints [ [ 'a', 'b' ], [ 'c', 'd' ], [ 'e' ] ]

上面每个// nbr的解释:

  1. 如果之前的值,即之前返回的块数组为空,或者如果最后一个前一个块具有chunkSize项,则创建一个新块
  2. 将新块添加到现有块的数组中
  3. 否则,当前块是块数组中的最后一个块
  4. 将当前值添加到块中
  5. 返回修改后的块数组
  6. 通过传递一个空数组初始化缩减

基于chunkSize的咖喱:

var chunk3 = function(array) {return chunk(3, array);};
console.log(chunk3(['a', 'b', 'c', 'd', 'e']));// prints [ [ 'a', 'b', 'c' ], [ 'd', 'e' ] ]

您可以将chunk()函数添加到全局Array对象:

Object.defineProperty(Array.prototype, 'chunk', {value: function(chunkSize) {return this.reduce(function(previous, current) {var chunk;if (previous.length === 0 ||previous[previous.length -1].length === chunkSize) {chunk = [];previous.push(chunk);}else {chunk = previous[previous.length -1];}chunk.push(current);return previous;}, []);}});
console.log(['a', 'b', 'c', 'd', 'e'].chunk(4));// prints [ [ 'a', 'b', 'c' 'd' ], [ 'e' ] ]

这是一个使用Reduce的ES6版本

const perChunk = 2 // items per chunk
const inputArray = ['a','b','c','d','e']
const result = inputArray.reduce((resultArray, item, index) => {const chunkIndex = Math.floor(index/perChunk)
if(!resultArray[chunkIndex]) {resultArray[chunkIndex] = [] // start a new chunk}
resultArray[chunkIndex].push(item)
return resultArray}, [])
console.log(result); // result: [['a','b'], ['c','d'], ['e']]

您已经准备好链接进一步的map/duce转换。您的输入数组保持不变


如果你更喜欢一个更短但可读性较差的版本,你可以在混合中加入一些concat来获得相同的最终结果:

inputArray.reduce((all,one,i) => {const ch = Math.floor(i/perChunk);all[ch] = [].concat((all[ch]||[]),one);return all}, [])

您可以使用余数运算符将连续的项目放入不同的块中:

const ch = (i % perChunk);

那么,一个很好的函数将是:

function chunk(arr,times){if(times===null){var times = 10} //Fallback for users wanting to use the default of tenvar tempArray = Array() //Array to be populated with chunksfor(i=0;i<arr.length/times;i++){tempArray[i] = Array() //Sub-Arrays        //Repeats for each chunkfor(j=0;j<times;j++){if(!(arr[i*times+j]===undefined)){tempArray[i][j] = arr[i*times+j]//Populate Sub-  Arrays with chunks}else{j = times //Stop loopi = arr.length/times //Stop loop}}}return tempArray //Return the populated and chunked array}

用法将是:

chunk(array,sizeOfChunks)

我评论它,所以你可以理解发生了什么。

(格式有点偏离,我在移动设备上编写了这个)

这是一个使用ImMutableJS的解决方案,其中items是一个不可变列表,size是所需的分组大小。

const partition = ((items, size) => {return items.groupBy((items, i) => Math.floor(i/size))})

ES6发电机版本

function* chunkArray(array,size=1){var clone = array.slice(0);while (clone.length>0)yield clone.splice(0,size);};var a = new Array(100).fill().map((x,index)=>index);for(const c of chunkArray(a,10))console.log(c);
results = []chunk_size = 10while(array.length > 0){results.push(array.splice(0, chunk_size))}

这是一个仅使用递归和切片()的非突变解决方案。

const splitToChunks = (arr, chunkSize, acc = []) => (arr.length > chunkSize ?splitToChunks(arr.slice(chunkSize),chunkSize,[...acc, arr.slice(0, chunkSize)]) :[...acc, arr]);

然后简单地像splitToChunks([1, 2, 3, 4, 5], 3)一样使用它来获得[[1, 2, 3], [4, 5]]

这里有一个小提琴供您尝试:https://jsfiddle.net/6wtrbx6k/2/

她是一个使用@Blazemonger解决方案的简单解决方案

function array_chunk(arr, size){// initialize varsvar i,j = arr.length,tempArray = [];// loop through and jump based on sizefor (i=0; i<j; i+=size) {// slice chunk of arr and push to tempArraytempArray.push(arr.slice(i,i+size));}// return temp array (chunck)return tempArray}

这让管道为我流动,希望这能帮助其他人。:)

这是缩减()方法的另一个解决方案,尽管与其他示例略有不同。希望我的解释也更清晰。

var arr = [0, 1, 2, 3, 4, 5, 6, 7];var chunkSize = 3;
arr = arr.reduce((acc, item, idx) => {let group = acc.pop();if (group.length == chunkSize) {acc.push(group);group = [];}group.push(item);acc.push(group);return acc;}, [[]]);
console.log(arr); //Prints [[0, 1, 2], [3, 4, 5], [6, 7]]


补充说明

我们调用一个减速器,对于数组中的每个项目,它获取累加器的最后一项pop()。请记住,此项是一个数组,最多可组合chunkSize个项目(3在这个例子中)。

当且仅当该组的数组长度等于chunksize时,我们需要将该组重新插入到累加器中并创建一个新组。

然后我们将当前item推送到我们的group数组(其中可能已经包含来自前面步骤的0、1或2个项目)中。将当前item插入到group中后,我们需要将group重新插入到更大的集合中。

这个过程将重复,直到我们遍历了arr中的所有项目。

请注意,我们还为减速器提供了[[]]数组中空数组的起始值。

这将是我对这个话题的贡献。我想.reduce()是最好的方法。

var segment = (arr, n) => arr.reduce((r,e,i) => i%n ? (r[r.length-1].push(e), r): (r.push([e]), r), []),arr = Array.from({length: 31}).map((_,i) => i+1);res = segment(arr,7);console.log(JSON.stringify(res));

但是上面的实现不是很有效,因为.reduce()贯穿了所有arr函数。更有效的方法(非常接近最快的命令式解决方案)是,迭代缩小的(要分块的)数组,因为我们可以在Math.ceil(arr/n);之前提前计算它的大小。一旦我们有了像Array(Math.ceil(arr.length/n)).fill();这样的空结果数组,剩下的就是将arr数组的切片映射到它中。

function chunk(arr,n){var r = Array(Math.ceil(arr.length/n)).fill();return r.map((e,i) => arr.slice(i*n, i*n+n));}
arr = Array.from({length: 31},(_,i) => i+1);res = chunk(arr,7);console.log(JSON.stringify(res));

到目前为止,一切都很好,但我们仍然可以进一步简化上面的片段。

var chunk = (a,n) => Array.from({length: Math.ceil(a.length/n)}, (_,i) => a.slice(i*n, i*n+n)),arr   = Array.from({length: 31},(_,i) => i+1),res   = chunk(arr,7);
console.log(JSON.stringify(res));

这是chunk()函数的整洁和优化实现。假设默认块大小为10

var chunk = function(list, chunkSize) {if (!list.length) {return [];}if (typeof chunkSize === undefined) {chunkSize = 10;}
var i, j, t, chunks = [];for (i = 0, j = list.length; i < j; i += chunkSize) {t = list.slice(i, i + chunkSize);chunks.push(t);}
return chunks;};
//calling functionvar list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];var chunks = chunk(list);

使用ES6的拼接版本

let  [list,chunkSize] = [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 6];list = [...Array(Math.ceil(list.length / chunkSize))].map(_ => list.splice(0,chunkSize))console.log(list);

对于函数式解决方案,使用Ramda

其中popularProducts是您的输入数组,5是块大小

import splitEvery from 'ramda/src/splitEvery'
splitEvery(5, popularProducts).map((chunk, i) => {// do something with chunk
})

我的目标是在纯ES6中创建一个简单的非突变解决方案。javascript的特性使得在映射之前有必要填充空数组:-(

function chunk(a, l) {return new Array(Math.ceil(a.length / l)).fill(0).map((_, n) => a.slice(n*l, n*l + l));}

这个带有递归的版本看起来更简单,更引人注目:

function chunk(a, l) {if (a.length == 0) return [];else return [a.slice(0, l)].concat(chunk(a.slice(l), l));}

ES6的荒谬的弱数组函数使得很好的谜题:-)

基于Array.prototypereducepush方法的ES6单行方法:

const doChunk = (list, size) => list.reduce((r, v) =>(!r.length || r[r.length - 1].length === size ?r.push([v]) : r[r.length - 1].push(v)) && r, []);
console.log(doChunk([0,1,2,3,4,5,6,7,8,9,10,11,12], 5));// [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12]]

这是我能想到的最有效和最直接的解决方案:

function chunk(array, chunkSize) {let chunkCount = Math.ceil(array.length / chunkSize);let chunks = new Array(chunkCount);for(let i = 0, j = 0, k = chunkSize; i < chunkCount; ++i) {chunks[i] = array.slice(j, k);j = k;k += chunkSize;}return chunks;}

有很多答案,但这是我使用的:

const chunk = (arr, size) =>arr.reduce((acc, _, i) =>(i % size)? acc: [...acc, arr.slice(i, i + size)], [])
// USAGEconst numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]chunk(numbers, 3)
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

首先,在将索引除以块大小时检查余数。

如果有余数,则只需返回累加器数组。

如果没有余数,则索引可被块大小整除,因此从原始数组中获取一个切片(从当前索引开始)并将其添加到累加器数组中。

因此,每次Reduce迭代返回的累加器数组看起来像这样:

// 0: [[1, 2, 3]]// 1: [[1, 2, 3]]// 2: [[1, 2, 3]]// 3: [[1, 2, 3], [4, 5, 6]]// 4: [[1, 2, 3], [4, 5, 6]]// 5: [[1, 2, 3], [4, 5, 6]]// 6: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]// 7: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]// 8: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]// 9: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

试试这个:

var oldArray =  ["Banana", "Orange", "Lemon", "Apple", "Mango", "Banana", "Orange", "Lemon", "Apple", "Mango", "Banana", "Orange", "Lemon", "Apple", "Mango", "Banana", "Orange", "Lemon", "Apple", "Mango", "Banana", "Orange", "Lemon", "Apple", "Mango"];
var newArray = [];
while(oldArray.length){let start = 0;let end = 10;newArray.push(oldArray.slice(start, end));oldArray.splice(start, end);} 
console.log(newArray);

我更喜欢使用拼接方法而不是切片。此解决方案使用数组长度和块大小来创建循环计数,然后在数组上循环,由于每一步中的拼接,每次操作后数组都会变小。

    function chunk(array, size) {let resultArray = [];let chunkSize = array.length/size;for(i=0; i<chunkSize; i++) {resultArray.push(array.splice(0, size));}return console.log(resultArray);}chunk([1,2,3,4,5,6,7,8], 2);

如果您不想改变原始数组,您可以使用扩展运算符克隆原始数组,然后使用该数组来解决问题。

    let clonedArray = [...OriginalArray]

这应该是一个简单的答案,没有许多数学并发症。

function chunkArray(array, sizeOfTheChunkedArray) {const chunked = [];
for (let element of array) {const last = chunked[chunked.length - 1];
if(!last || last.length === sizeOfTheChunkedArray) {chunked.push([element])} else {last.push(element);}}return chunked;}

嗨,试试这个-

 function split(arr, howMany) {var newArr = []; start = 0; end = howMany;for(var i=1; i<= Math.ceil(arr.length / howMany); i++) {newArr.push(arr.slice(start, end));start = start + howMany;end = end + howMany}console.log(newArr)}split([1,2,3,4,55,6,7,8,8,9],3)

这是一个带有尾递归和数组解构的版本。

远非最快的表现,但我只是觉得js现在可以做到这一点很有趣。即使它没有为此进行优化:(

const getChunks = (arr, chunk_size, acc = []) => {if (arr.length === 0) { return acc }const [hd, tl] = [ arr.slice(0, chunk_size), arr.slice(chunk_size) ]return getChunks(tl, chunk_size, acc.concat([hd]))}
// USAGEconst my_arr = [1,2,3,4,5,6,7,8,9]const chunks = getChunks(my_arr, 2)console.log(chunks) // [[1,2],[3,4], [5,6], [7,8], [9]]

ES6传播功能#ohmy#ftw

const chunk =(size, xs) =>xs.reduce((segments, _, index) =>index % size === 0? [...segments, xs.slice(index, index + size)]: segments,[]);
console.log( chunk(3, [1, 2, 3, 4, 5, 6, 7, 8]) );

我认为这是一个使用ES6语法的不错的递归解决方案:

const chunk = function(array, size) {if (!array.length) {return [];}const head = array.slice(0, size);const tail = array.slice(size);
return [head, ...chunk(tail, size)];};
console.log(chunk([1,2,3], 2));

以下ES2015方法无需定义函数即可直接在匿名数组上工作(例如块大小为2):

[11,22,33,44,55].map((_, i, all) => all.slice(2*i, 2*i+2)).filter(x=>x.length)

如果你想为此定义一个函数,你可以这样做(改进K._对Blazemonger的回答的评论):

const array_chunks = (array, chunk_size) => array.map((_, i, all) => all.slice(i*chunk_size, (i+1)*chunk_size)).filter(x => x.length)

使用发电机

function* chunks(arr, n) {for (let i = 0; i < arr.length; i += n) {yield arr.slice(i, i + n);}}
let someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]console.log([...chunks(someArray, 2)]) // [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

可以像这样用TypeScript键入:

function* chunks<T>(arr: T[], n: number): Generator<T[], void> {for (let i = 0; i < arr.length; i += n) {yield arr.slice(i, i + n);}}

我的技巧是使用parseInt(i/chunkSize)parseInt(i%chunkSize),然后填充数组

// filling itemslet array = [];for(let i = 0; i< 543; i++)array.push(i); 
// printing the splitted arrayconsole.log(getSplittedArray(array, 50)); 
// get the splitted arrayfunction getSplittedArray(array, chunkSize){let chunkedArray = [];for(let i = 0; i<array.length; i++){try{chunkedArray[parseInt(i/chunkSize)][parseInt(i%chunkSize)] = array[i];}catch(e){chunkedArray[parseInt(i/chunkSize)] = [];chunkedArray[parseInt(i/chunkSize)][parseInt(i%chunkSize)] = array[i];}}return chunkedArray;}

这是一个递归解决方案,即尾调用优化。

const splitEvery = (n, xs, y=[]) =>xs.length===0 ? y : splitEvery(n, xs.slice(n), y.concat([xs.slice(0, n)]))
console.log(splitEvery(2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))

使用Lodash中的chunk

lodash.chunk(arr,<size>).forEach(chunk=>{console.log(chunk);})

这是我使用的,它可能不是超级快,但它是紧凑和简单:

let chunksplit = (stream, size) => stream.reduce((chunks, item, idx, arr) => (idx % size == 0) ? [...chunks, arr.slice(idx, idx + size)] : chunks, []);//if the index is a multiple of the chunksize, add new array
let testArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22];
document.write(JSON.stringify( chunksplit(testArray, 5) ));//using JSON.stringify for the nested arrays to be shown

这个问题可能有很多解决方案。

我最喜欢的一个是:

function chunk(array, size) {const chunked = [];
for (element of array){let last = chunked[chunked.length - 1];
if(last && last.length != size){last.push(element)}else{chunked.push([element])}}   
return chunked;}

function chunk1(array, size) {const chunked = [];
let index = 0;
while(index < array.length){chunked.push(array.slice(index,index+ size))index += size;}return chunked;}
console.log('chunk without slice:',chunk([1,2,3,4,5,5],2));console.log('chunk with use of slice funtion',chunk1([1,2,3,4,5,6],2))

这是一个示例,我将数组拆分为2个元素的块,只需将块从数组中拼接出来,直到原始数组为空。

    const array = [86,133,87,133,88,133,89,133,90,133];const new_array = [];
const chunksize = 2;while (array.length) {const chunk = array.splice(0,chunksize);new_array.push(chunk);}
console.log(new_array)

使用Array.prototype.reduce()的另一个解决方案:

const chunk = (array, size) =>array.reduce((acc, _, i) => {if (i % size === 0) acc.push(array.slice(i, i + size))return acc}, [])
// Usage:const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]const chunked = chunk(numbers, 3)console.log(chunked)

这个解决方案与Steve Holgado的解决方案非常相似。然而,因为这个解决方案没有利用数组扩展,也没有在还原函数中创建新的数组,所以它比另一个解决方案更快(参见jsperf测试),主观上更具可读性(语法更简单)。

在每一次第n迭代中(其中n=size;从第一次迭代开始),累加器数组(acc)被附加一个数组块(array.slice(i, i + size)),然后返回。在其他迭代中,累加器数组按原样返回。

如果size为零,则该方法返回一个空数组。如果size为负,则该方法返回破碎的结果。因此,如果在您的情况下需要,您可能需要对负或非正的size值做些什么。


如果速度在你的情况下很重要,一个简单的for循环会比使用reduce()更快(见jsperf测试),有些人可能会发现这种风格更具可读性:

function chunk(array, size) {// This prevents infinite loopsif (size < 1) throw new Error('Size must be positive')
const result = []for (let i = 0; i < array.length; i += size) {result.push(array.slice(i, i + size))}return result}

Neat and clean easy to understand

 let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];let len = nums.length ;
const chunkArr = (arr, chunkNo) => {let newArr = [];for(let i = 0; i < len; i++){if(nums[0] !== "" && nums[0] !== undefined ){let a = nums.splice(0,chunkNo) ;newArr.push(a);}}return newArr ;}console.log(chunkArr(nums, 5));

使用Array.prototype.splice()并拼接它,直到数组具有元素。

Array.prototype.chunk = function(size) {let result = [];    
while(this.length) {result.push(this.splice(0, size));}        
return result;}
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];console.log(arr.chunk(2));

更新

Array.prototype.splice()填充原始数组,执行chunk()后,原始数组(arr)变为[]

因此,如果您想保持原始数组不变,请将arr数据复制并保留到另一个数组中并做同样的事情。

Array.prototype.chunk = function(size) {let data = [...this];let result = [];    
while(data.length) {result.push(data.splice(0, size));}
return result;}
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];console.log('chunked:', arr.chunk(2));console.log('original', arr);

P. S:感谢@mts-knn提及此事。

如果您使用Undercore JS,只需使用:

var result = _.chunk(arr,elements_per_chunk)

无论如何,大多数项目已经使用下划线作为依赖项。

纯javascript中的一行:

function chunks(array, size) {return Array.apply(0,{length: Math.ceil(array.length / size)}).map((_, index) => array.slice(index*size, (index+1)*size))}
// The following will group letters of the alphabet by 4console.log(chunks([...Array(26)].map((x,i)=>String.fromCharCode(i + 97)), 4))

我推荐使用洛达什。分块是那里许多有用的函数之一。说明:

npm i --save lodash

在您的项目中包含:

import * as _ from 'lodash';

用法:

const arrayOfElements = ["Element 1","Element 2","Element 3", "Element 4", "Element 5","Element 6","Element 7","Element 8","Element 9","Element 10","Element 11","Element 12"]const chunkedElements = _.chunk(arrayOfElements, 10)

您可以在此处找到我的示例:https://playcode.io/659171/

如果这对任何人都有用,这可以在RxJS 6中非常简单地完成:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];from(arr).pipe(bufferCount(3)).subscribe(chunk => console.log(chunk));

产出:[1,2,3][4,5,6][7,8,9][10,11,12][13,14,15][16]

单行

const chunk = (a,n)=>[...Array(Math.ceil(a.length/n))].map((_,i)=>a.slice(n*i,n+n*i));

对于TypeScript

const chunk = <T>(arr: T[], size: number): T[][] =>[...Array(Math.ceil(arr.length / size))].map((_, i) =>arr.slice(size * i, size + size * i));

DEMO

const chunk = (a,n)=>[...Array(Math.ceil(a.length/n))].map((_,i)=>a.slice(n*i,n+n*i));document.write(JSON.stringify(chunk([1, 2, 3, 4], 2)));

按组数划分的块

const part=(a,n)=>[...Array(n)].map((_,i)=>a.slice(i*Math.ceil(a.length/n),(i+1)*Math.ceil(a.length/n)));

对于TypeScript

const part = <T>(a: T[], n: number): T[][] => {const b = Math.ceil(a.length / n);return [...Array(n)].map((_, i) => a.slice(i * b, (i + 1) * b));};

DEMO

const part = (a, n) => {const b = Math.ceil(a.length / n);return [...Array(n)].map((_, i) => a.slice(i * b, (i + 1) * b));};
document.write(JSON.stringify(part([1, 2, 3, 4, 5, 6], 2))+'<br/>');document.write(JSON.stringify(part([1, 2, 3, 4, 5, 6, 7], 2)));

我是这样解决的:

const chunks = [];const chunkSize = 10;for (let i = 0; i < arrayToSplit.length; i += chunkSize) {const tempArray = arrayToSplit.slice(i, i + chunkSize);chunks.push(tempArray);}

您可以使用此ES6chunk函数,该函数易于使用:

const chunk = (array, size) =>Array.from({length: Math.ceil(array.length / size)}, (value, index) => array.slice(index * size, index * size + size));
const itemsPerChunk = 3;const inputArray = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const newArray = chunk(inputArray, itemsPerChunk);console.log(newArray.length); // 3,
document.write(JSON.stringify(newArray)); //  [ [ 'a', 'b', 'c' ], [ 'd', 'e', 'f' ], [ 'g' ] ]

这个聚会已经很晚了,但我解决了一个类似的问题,使用.join("")将数组转换为一个巨大的字符串,然后使用regex将.match(/.{1,7}/)转换为最大长度为7的子字符串数组。

const arr = ['abc', 'def', 'gh', 'ijkl', 'm', 'nopq', 'rs', 'tuvwx', 'yz'];const arrayOfSevens = arr.join("").match(/.{1,7}/g);// ["abcdefg", "hijklmn", "opqrstu", "vwxyz"]

看看它在速度测试中对其他方法的表现会很有趣

晚了,这是我的两分钱。就像很多人说的,我首先会想到

chunker = (a,n) => [...Array(Math.ceil(a.length/n))].map((v,i) => a.slice(i*n, (i+1)*n))

但我更喜欢和没有在这里看到的是:

chunker = (n) => (r,v,i) => (c = Math.floor(i/n), (r[c] = r[c] || []).push(v), r)
console.log(arr.reduce(chunker(3), []))

具有较长变体

chunker = (a, n) => a.reduce((r,v,i) => {c = Math.floor(i/n); // which chunk it belongs to(r[c] = r[c] || []).push(v)return r}, [])
console.log(chunker(arr, 3))

解释

  1. 常见的答案将首先确定块的数量,然后根据块的大小和每个块的大小获取原始数组的切片

  2. chunker减速机函数将遍历每个元素并将其放入根据评估的chunk数组中。

性能几乎相同,对于我所看到的,Reduce方法平均慢4%。

PS:duce(ing)具有轻松更改分组条件的优势。在问题和示例中,标准是相邻的单元格(映射使用切片)。但是你可以在“循环”中进行,例如,使用mod(%运算符)或任何其他数学公式

重新阅读它让我看到公式也可以是一个参数,导致更通用的解决方案,并需要2个函数来实现答案:

splitter = (a, f) => a.reduce((r,v,i) => { // math formula and/or functionc = f(v, i) || 0; // custom formula, receiving each value and index(r[c] = r[c] || []).push(v)return r}, [])
chunker = (a, n) => splitter(a, (v,i) => Math.floor(i/n))
console.log(chunker(arr, 3))console.log(splitter(arr, (v,i) => v % 2))  // is it even or odd?

splitter也可以用于制作命名数组,也就是对象,函数返回字符串而不是数字:)

您可以使用Array.prototype.reduce函数在一行中执行此操作。

let arr = [1,2,3,4];function chunk(arr, size){let result = arr.reduce((rows, key, index) => (index % size == 0 ? rows.push([key]) : rows[rows.length-1].push(key)) && rows, []);return result;}        
console.log(chunk(arr,2));

TypeScript版本。演示是101个随机uid分成10组

const idArrayLengthLimit = 10;const randomOneHundredOneIdArray = Array.from(Array(101).keys()).map(() => generateUid(5));
function generateUid(length: number) {const uidString: string[] = [];const uidChars = 'abcdefghijklmnopqrstuvwxyz0123456789';for (let i = 0; i < length; i++) {uidString.push(uidChars.charAt(Math.floor(Math.random() * uidChars.length)));}return uidString.join('');}
for (let i = 0; i < randomOneHundredOneIdArray.length; i++) {if(i % idArrayLengthLimit === 0){const result = randomOneHundredOneIdArray.filter((_,id) => id >= i && id < i + idArrayLengthLimit);// Observe resultconsole.log(result);}}

const array = ['a', 'b', 'c', 'd', 'e'];const size = 2;const chunks = [];while (array.length) {chunks.push(array.splice(0, size));}console.log(chunks);

例子源数组
没有同时生成所有块。(内存保护!)

const array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21];
const chunkSize = 4for (var i = 0; i < array.length; i += chunkSize) {const chunk = array.slice(i, i + chunkSize);console.log('chunk=',chunk)// do whatever}console.log('src array didnt changed. array=',array)

js

function splitToBulks(arr, bulkSize = 20) {const bulks = [];for (let i = 0; i < Math.ceil(arr.length / bulkSize); i++) {bulks.push(arr.slice(i * bulkSize, (i + 1) * bulkSize));}return bulks;}
console.log(splitToBulks([1, 2, 3, 4, 5, 6, 7], 3));

打字稿

function splitToBulks<T>(arr: T[], bulkSize: number = 20): T[][] {const bulks: T[][] = [];for (let i = 0; i < Math.ceil(arr.length / bulkSize); i++) {bulks.push(arr.slice(i * bulkSize, (i + 1) * bulkSize));}return bulks;}

这里有一个更具体的案例,可能有人会觉得有价值。我还没有看到这里提到它。

如果您不想要常量/甚至块大小,而是想指定数组拆分的索引怎么办?在这种情况下,您可以使用:

const splitArray = (array = [], splits = []) => {array = [...array]; // make shallow copy to avoid mutating originalconst chunks = []; // collect chunksfor (const split of splits.reverse()) chunks.push(array.splice(split)); // go backwards through split indices and lop off end of arraychunks.push(array); // add last remaining chunk (at beginning of array)return chunks.reverse(); // restore chunk order};

然后:

splitArray([1, 2, 3, 4, 5, 6, 7, 8, 9], [4, 6])// [ [1, 2, 3, 4] , [5, 6] , [7, 8, 9] ]

请注意,如果您给它非升序/重复/负/非整数/etc拆分索引,这会做一些有趣的事情。您可以为这些边缘情况添加检查(例如Array.from(new Set(array))来消除重复。

我尝试了一个递归函数…

const chunk = (arr, n) =>arr.length ? [arr.slice(0, n), ...chunk(arr.slice(n), n)] : [];

…这是好的和短的,但似乎需要大约256倍的长度@陈志立为1,000个元素,和1,058×10,000个元素!

一个有效的解决方案是将解决方案与切片按索引推送连接起来,将解决方案拆分为块:

function splitChunks(sourceArray, chunkSize) {if(chunkSize <= 0)throw "chunkSize must be greater than 0";let result = [];for (var i = 0; i < sourceArray.length; i += chunkSize) {result[i / chunkSize] = sourceArray.slice(i, i + chunkSize);}return result;}
let ar1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
console.log("Split in chunks with 4 size", splitChunks(ar1, 4));console.log("Split in chunks with 7 size", splitChunks(ar1, 7));

我最喜欢的是带有附加功能getChunks的生成器generateChunks来执行生成器。

function* generateChunks(array, size) {let start = 0;while (start < array.length) {yield array.slice(start, start + size);start += size;}}
function getChunks(array, size) {return [...generateChunks(array, size)];}
console.log(getChunks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 3)) // [ [ 0, 1, 2 ], [ 3, 4, 5 ], [ 6, 7, 8 ], [ 9 ] ]

作为这里的补充,生成器generatePartitions具有进一步的函数getPartitions以获得大小相等的n个数组。

function generatePartitions(array, count) {return generateChunks(array, Math.ceil(array.length / count));}
function getPartitions(array, count) {return [...generatePartitions(array, count)];}
console.log(getPartitions([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 3)) // [ [ 0, 1, 2, 3 ], [ 4, 5, 6, 7 ], [ 8, 9 ] ]

与许多其他解决方案相比,生成器的一个优点是不会创建多个不必要的数组。

在js,

const splitInChunks = (arr,n) => {let chunksArr = [];if(arr !=null && arr!= undefined){for(i=0; i<arr.length;i+=n){if(arr.length-i>=n)chunksArr.push(arr.slice(i,i+n))elsechunksArr.push(arr.slice(i,arr.length))}return chunksArr}}

当前热门答案的问题是它们会产生不平衡的块。例如,当前接受的答案将把一个101个元素的数组分配到10个大小为10的块中,然后是1个大小为1的块。

使用一些模块化算法可以创建永远不会相差超过1的统一块大小:

function split_array(a, nparts) {const quot = Math.floor(a.length / nparts)const rem = a.length % npartsvar parts = []for (var i = 0; i < nparts; ++i) {const begin = i * quot + Math.min(rem, i)const end = begin + quot + (i < rem)parts.push(a.slice(begin, end))}return parts}
var chunks = split_array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3)console.log(JSON.stringify(chunks))

输出:

[[1,2,3,4],[5,6,7],[8,9,10]]

(复制自相关答案

使用源数组的突变:

let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], aa = [], xwhile((x = a.splice(0, 2)).length) aa.push(x)
// aa == [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ], [ 9 ] ]// a == []

在不改变源数组的情况下:

let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], aa = []for(let i = 0; i < a.length; i += 2) aa.push(a.slice(i, i + 2))
// aa == [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ], [ 9 ] ]// a == [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

最有效的方法是将数组视为Iterable,并进行懒惰分页。这样,它将仅在请求时产生数据。下面的代码使用it-ops库中的运算符页面

import {pipe, page} from 'iter-ops';
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // some input data
const i = pipe(arr, page(2)); //=> Iterable<number>
console.log(...i); //=> [ 1, 2 ] [ 3, 4 ] [ 5, 6 ] [ 7, 8 ] [ 9 ]

对任何IterableAsyncIterable的工作方式相同。


注:我是图书馆的作者。