将数组分成块

1005

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

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

如何将一个数组分成多个小数组,每个小数组最多包含10个元素?


参见 如何将长数组拆分为较小的数组使用underscore.js拆分javascript数组 (以及链接问题中的许多重复项 linked questions)。 - Bergi
可能是将JS数组分成N个数组的重复问题。 - T J
70
对于lodash用户,您需要查找_.chunk - Ulysse BN
如果你需要最后一个块的最小大小,这里有几个选项:https://stackoverflow.com/questions/57908133/splitting-an-array-up-into-chunks-of-a-given-size-with-a-minimum-chunk-size - Ahmet Cetin
我创建了一个解决方案,合并了最佳答案:https://dev59.com/4Goy5IYBdhLWcg3wg-Ym#71483760 - Fernando Leal
@webatrisans 这回答了你的问题。如果你使用正确的标签,那么我们更容易给出正确的指针指向正确的重复内容。 - RiggsFolly
84个回答

31

使用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 loops
  if (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
}

你的 reduce 示例是迄今为止最简洁的方法。 - Alex

28

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

const chunk = (arr, size) =>
  arr
    .reduce((acc, _, i) =>
      (i % size)
        ? acc
        : [...acc, arr.slice(i, i + size)]
    , [])

// USAGE
const 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]]

很好的解决方案和漂亮的迭代可视化。我最终得出了一个非常相似的解决方案,并将其发布为答案:https://dev59.com/4Goy5IYBdhLWcg3wg-Ym#60779547 - Matias Kinnunen

25

我认为这是一个很好的使用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));


22

一句话总结

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)
  );

演示

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));
};

演示

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)));


1
谢谢!一行代码是最好的答案! - Unchained
1
chunk([1,2,3,4],2) 的结果是 [ [ 1, 2 ], [ 3, 4 ], [] ]。这个结果对我来说似乎不正确。 - Hans Bouwmeester
无法重现您的结果@HansBouwmeeste。https://u.davwheat.dev/3Om2Au5D.png - David Wheatley
1
已经修复了,是我的问题,我应该提到的。 - nkitku
1
@David Wheatley。确认。我尝试了最新版本,现在正常工作。 - Hans Bouwmeester
你应该更倾向于 @Benny Neugebauer 提供的答案,因为它不创建任何中间数组。这个例子实际上创建了两个额外的数组。第一个是 Array(n),第二个是由 [... ] 包装第一个数组产生的。由 map() 创建出来的结果数组并不是这两个数组之一。你可以使用 Array(n).fill(0) 来替换 [...Array(n)] 以消除一个数组。 - Thomas Urban

19

好的,让我们从一道比较难的题目开始:

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);

那么我们有这个紧缩的 reducer 函数:

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);

在IE8中不起作用。 - Nadeemmnn Mohd
5
哈!我喜欢那个小猫的评论。对于没有提供任何有建设性的意见,我感到抱歉 :) - 29er
我会使用 (p[i/n|0] || (p[i/n|0] = [])) 这个表达式,这样如果不必要的话就不需要进行赋值操作。 - yckart
对于柯里化(部分应用应用函数),您需要在绑定中使用 thisArg = null,例如 chunker.bind(null, 3)文档 Function.prototype.bind() - Michal Miky Jankovský

14

我旨在创建一个纯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中极其脆弱的数组函数经常用于制作有趣的谜题 :-)


1
我也写了类似的代码。如果你从 fill 中删除 0,它仍然可以工作,这样 fill 看起来更合理,个人认为。 - Coert Grobbelaar

12

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

var result = [];

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

使用 TypedArray 时,
var result = [];

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

@A1rPun 对不起,我没有在那里添加注释。是的,TypedArray 没有 slice 方法,我们可以使用 subarray 代替 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray - zhiyelee

10
使用 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);

附言:感谢 @mts-knn 提到这件事情。


1
请注意,splice 方法会修改原始数组。如果您在代码片段的末尾添加 console.log(arr);,它将记录 [],即 arr 将成为空数组。 - Matias Kinnunen

10
以下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)

  • short and nice
- Kamil Kiełczewski

10

我推荐使用lodash。在那里,分块是其中许多有用的功能之一。 说明:

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/


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