将JS数组拆分为N个数组


81

想象一下,我有一个像这样的JS数组:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

我想要的是将该数组拆分为N个较小的数组。例如:

split_list_in_n(a, 2)
[[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11]]

For N = 3:
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]

For N = 4:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11]]

For N = 5:
[[1, 2, 3], [4, 5], [6, 7], [8, 9], [10, 11]]

对于Python,我有这个:

def split_list_in_n(l, cols):
    """ Split up a list in n lists evenly size chuncks """
    start = 0
    for i in xrange(cols):
        stop = start + len(l[i::cols])
        yield l[start:stop]
        start = stop

对于JS,我可以提出的最佳解决方案是递归函数,但我不喜欢它,因为它既复杂又丑陋。这个内部函数返回一个像这样的数组[1,2,3,null,4,5,6,null,7,8],然后我必须再次循环并手动拆分它。(我的第一次尝试是返回此:[1、2、3,[4、5、6,[7、8、9]]],然后我决定使用null分隔符进行操作。)

function split(array, cols) {
    if (cols==1) return array;
    var size = Math.ceil(array.length / cols);
    return array.slice(0, size).concat([null]).concat(split(array.slice(size), cols-1));
}

这是一个jsfiddle:http : //jsfiddle.net/uduhH/

你会怎么做?谢谢!



1
您的split功能距离不远。您可以null通过添加两个数组包装器if (cols == 1) return [array]和删除业务return [array.slice(0, size)].concat(split(array.slice(size), cols-1))。我发现此递归版本比这里的大多数答案更具可读性。
Scott Sauyet '18

Answers:


133

您可以使切片“平衡”(子阵列的长度差异尽可能小)或“偶数”(除最后一个以外的所有子阵列具有相同的长度):

function chunkify(a, n, balanced) {
    
    if (n < 2)
        return [a];

    var len = a.length,
            out = [],
            i = 0,
            size;

    if (len % n === 0) {
        size = Math.floor(len / n);
        while (i < len) {
            out.push(a.slice(i, i += size));
        }
    }

    else if (balanced) {
        while (i < len) {
            size = Math.ceil((len - i) / n--);
            out.push(a.slice(i, i += size));
        }
    }

    else {

        n--;
        size = Math.floor(len / n);
        if (len % size === 0)
            size--;
        while (i < size * n) {
            out.push(a.slice(i, i += size));
        }
        out.push(a.slice(size * n));

    }

    return out;
}


///////////////////////

onload = function () {
    function $(x) {
        return document.getElementById(x);
    }

    function calc() {
        var s = +$('s').value, a = [];
        while (s--)
            a.unshift(s);
        var n = +$('n').value;
        $('b').textContent = JSON.stringify(chunkify(a, n, true))
        $('e').textContent = JSON.stringify(chunkify(a, n, false))
    }

    $('s').addEventListener('input', calc);
    $('n').addEventListener('input', calc);
    calc();
}
<p>slice <input type="number" value="20" id="s"> items into
<input type="number" value="6" id="n"> chunks:</p>
<pre id="b"></pre>
<pre id="e"></pre>


您的解决方案很简洁,它的功能与我的递归解决方案相同,但是没有那么多混乱。谢谢!
Tiago

3
就像魅力一样。.不错的解决方案
Vardan 2014年

嗨,@ georg,您能解释一下这行吗: var size = Math.ceil((len - i) / n--);
dpg5000 '16

@ dpg5000:切片下一个块时,其大小为剩余元素数(len - i)乘以剩余块数(n--
乔治

1
嗨@georg谢谢你。我将如何修改此代码,以确保除最后一个子数组外,所有子数组的长度均相等(除非除数不会导致余数,否则所有子数组均相等)。感谢您的帮助。
dpg5000 '16

18

我认为使用拼接的这种方式最干净:

splitToChunks(array, parts) {
    let result = [];
    for (let i = parts; i > 0; i--) {
        result.push(array.splice(0, Math.ceil(array.length / i)));
    }
    return result;
}

例如,对于parts = 3,您将占剩余部分的1/3,然后是1/2,然后是数组的其余部分。Math.ceil确保在元素数量不均匀的情况下,它们将排到最早的块。

(注意:这会破坏初始数组。)


16

function split(array, n) {
  let [...arr]  = array;
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, n));
  }
  return res;
}


2
对于n = 5和arr = [1、2、3、4、5、6、7、8、9、10、11],这不能按预期工作。
Tiago

1
这不会拆分为n个子数组,而只会拆分为n个长度的子数组。
dpg5000 '16

1
请添加一些解释,说明此代码为何有助于OP。这将为将来的观众提供一个答案。有关更多信息,请参见如何回答
异端猴子

12

我只是对该算法进行了迭代实现:http : //jsfiddle.net/ht22q/。它通过了您的测试用例。

function splitUp(arr, n) {
    var rest = arr.length % n, // how much to divide
        restUsed = rest, // to keep track of the division over the elements
        partLength = Math.floor(arr.length / n),
        result = [];

    for(var i = 0; i < arr.length; i += partLength) {
        var end = partLength + i,
            add = false;

        if(rest !== 0 && restUsed) { // should add one element for the division
            end++;
            restUsed--; // we've used one division element now
            add = true;
        }

        result.push(arr.slice(i, end)); // part of the array

        if(add) {
            i++; // also increment i in the case we added an extra element for division
        }
    }

    return result;
}

1
(这按预期工作,但我只能选择一个答案作为正确答案)嗨!感谢您的帮助。很好地思考如何使用其余的内容。
Tiago

7

您可以将其简化为矩阵。下面的示例将数组(arr)拆分为两个位置的数组的矩阵。如果需要其他尺寸,只需在第二行更改2值:

target.reduce((memo, value, index) => {
  if (index % 2 === 0 && index !== 0) memo.push([])
  memo[memo.length - 1].push(value)
  return memo
}, [[]])

希望能帮助到你!

编辑:因为有些人仍在评论这不能回答问题,因为我正在固定每个块大小,而不是我想要的块数。这里是代码,解释了我要在注释部分中解释的内容:使用target.length

// Chunk function

const chunk = (target, size) => {
  return target.reduce((memo, value, index) => {
    // Here it comes the only difference
    if (index % (target.length / size) == 0 && index !== 0) memo.push([])
    memo[memo.length - 1].push(value)
    return memo
  }, [[]])
}

// Usage

write(chunk([1, 2, 3, 4], 2))
write(chunk([1, 2, 3, 4], 4))

// For rendering pruposes. Ignore
function write (content) { document.write(JSON.stringify(content), '</br>') }


2
哇,简洁明了的方法!爱它!做得好!:-)
Philippe Monnet

1
我喜欢这种技术,但无法回答问题。它返回任意数量的x大小的块,而问题是要求x数量的均匀大小的块。
乔迪·沃伦

3
喜欢这个 !!!我已经重构以返回大小均匀的块了 function splitArr(arr, n) { return arr.reduce(function (a, i) { if (a[a.length - 1].length >= arr.length / n) { a.push([]) } a[a.length - 1].push(i) return a; }, [[]]) }
davide andreazzini

3
绝对不能回答这个问题。
macdelacruz

1
简洁而又非常聪明,我更喜欢用这种简单的模式来解决这个问题以及其他情况,谢谢!
Edmond Tamas

4

更新:2020年7月21日

我几年前给出的答案仅在originalArray.length<=时有效numCols。您也可以在下面使用类似此功能的东西,但这将创建与当前问题不太匹配的布局(水平排序,而不是垂直排序)。又名:[1,2,3,4]-> [[1,4],[2],[3]]。我知道这可能仍然可以提供价值,因此我将在此保留,但是我建议Senthe的答案

function splitArray(flatArray, numCols){
  const newArray = []
  for (let c = 0; c < numCols; c++) {
    newArray.push([])
  }
  for (let i = 0; i < flatArray.length; i++) {
    const mod = i % numCols
    newArray[mod].push(flatArray[i])
  }
  return newArray
}

2017年的原始答案:

这是一个古老的问题,但是由于vanillaJS不是必需的,并且许多人都试图用lodash / chunk解决这个问题,并且不会误会_.chunk实际的结果,因此这里使用了一个简洁而准确的解决方案lodash

(与接受的答案不同,即使originalArray.length< ,这也保证n列numCols

import _chunk from 'lodash/chunk'

/**
 * Split an array into n subarrays (or columns)
 * @param  {Array} flatArray Doesn't necessarily have to be flat, but this func only works 1 level deep
 * @param  {Number} numCols   The desired number of columns
 * @return {Array}
 */
export function splitArray(flatArray, numCols){
  const maxColLength = Math.ceil(flatArray.length/numCols)
  const nestedArray = _chunk(flatArray, maxColLength)
  let newArray = []
  for (var i = 0; i < numCols; i++) {
    newArray[i] = nestedArray[i] || []
  }
  return newArray
}

最后的for循环确保了所需数量的“列”。


当数组长度为4并且numCols为3时,此操作失败。尝试使用splitArray([1、2、3、4],3)并返回[[1、2],[3、4],[]]。
Pratik Ku​​lshreshth,

@PratikKulshreshth,您绝对正确。我将更新答案。对于任何感兴趣的人,我都喜欢Senthe的最佳答案:stackoverflow.com/a/51514813/1322810
Joao

2

递归方法,未经测试。

function splitArray(array, parts, out) {
    var
        len = array.length
        , partLen

    if (parts < len) {
        partLen = Math.ceil(len / parts);
        out.push(array.slice(0, partLen));
        if (parts > 1) {
            splitArray(array.slice(partLen), parts - 1, out);
        }
    } else {
        out.push(array);
    }
}

2

另一个递归效果很好,它不那么难看

function nSmaller(num, arr, sliced) {

    var mySliced = sliced || [];
    if(num === 0) {
        return sliced;
    }

    var len = arr.length,
        point = Math.ceil(len/num),
        nextArr = arr.slice(point);

    mySliced.push(arr.slice(0, point));
    nSmaller(num-1, nextArr, mySliced);

    return(mySliced);
}

2
function parseToPages(elements, pageSize = 8) {
    var result = [];
    while (elements.length) {
        result.push(elements.splice(0, pageSize));
    }
    return result;
}

3
请不要只是转储代码作为答案。添加说明这如何解决问题。
Mark Rotteveel

2

如果您碰巧事先知道所需块的大小,则可以使用一种非常优雅的ES6方法:

const groupsOfFour = ([a,b,c,d, ...etc]) =>
  etc.length? [[a,b,c,d], ...groupsOfFour(etc)] : [[a,b,c,d]];
  
console.log(groupsOfFour([1,2,3,4,1,2,3,4,1,2,3,4]));

我发现这种表示法非常有用,例如从中解析RGBA Uint8ClampedArray


除非这在n <4时失败:groupsOfFour( [ 1 ] )返回[ 1 , undefined, undefined, undefined ],而不是预期的(和期望的)[ [1] ]
尼古拉斯·凯里

1

可能更干净的方法如下(不使用任何其他库):

var myArray = [];
for(var i=0; i<100; i++){
  myArray.push(i+1);
}
console.log(myArray);

function chunk(arr, size){
  var chunkedArr = [];
  var noOfChunks = Math.ceil(arr.length/size);
  console.log(noOfChunks);
  for(var i=0; i<noOfChunks; i++){
    chunkedArr.push(arr.slice(i*size, (i+1)*size));
  }
   return chunkedArr;
}

var chunkedArr = chunk(myArray, 3);
console.log(chunkedArr);

我创建了自己的要分块的数组。你可以在这里找到代码

在lodash库中,我们还有一个方法“ chunk”,这很有用。希望能有所帮助


1
function splitArray(arr, numOfParts = 10){
        const splitedArray = []
        for (let i = 0; i < numOfParts;i++) {
            const numOfItemsToSplice = arr.length / 10;
            splitedArray.push(arr.splice(0, numOfItemsToSplice))
        }
        return splitedArray;
    }

0

我是这样制作的,它可以工作...

function splitArray(array, parts) {
    if (parts< array.length && array.length > 1 && array != null) {
        var newArray = [];
        var counter1 = 0;
        var counter2 = 0;

        while (counter1 < parts) {
            newArray.push([]);
            counter1 += 1;
        }

        for (var i = 0; i < array.length; i++) {
            newArray[counter2++].push(array[i]);
            if (counter2 > parts - 1)
                counter2 = 0;
        }

        return newArray;
    } else 
        return array;
}

0

检查我的这个数组拆分版本

// divide array
Array.prototype.divideIt = function(d){
    if(this.length <= d) return this;
    var arr = this,
        hold = [],
        ref = -1;
    for(var i = 0; i < arr.length; i++){
        if(i % d === 0){
            ref++;
        }
        if(typeof hold[ref] === 'undefined'){
            hold[ref] = [];
        }
        hold[ref].push(arr[i]);
    }

    return hold;
};

0

如果您知道要设置child_arrays.length,那么我认为此解决方案最佳:

function sp(size, arr){ //size - child_array.length
    var out = [],i = 0, n= Math.ceil((arr.length)/size); 
    while(i < n) { out.push(arr.splice(0, (i==n-1) && size < arr.length ? arr.length: size));  i++;} 
    return out;
}

调用fn:sp(2,[1、2、3、4、5、6、7、8、9、10、11])// 2-child_arrat.length

答案:[1、2],[3、4],[5、6],[7、8],[9、10],[11]


0

如果可以使用lodash并且希望使用函数式编程方法,这是我想出的:

const _ = require('lodash')

function splitArray(array, numChunks) {
  return _.reduce(_.range(numChunks), ({array, result, numChunks}, chunkIndex) => {
    const numItems = Math.ceil(array.length / numChunks)
    const items = _.take(array, numItems)
    result.push(items)
    return {
      array: _.drop(array, numItems),
      result,
      numChunks: numChunks - 1
    }
  }, {
    array,
    result: [],
    numChunks
  }).result
} 

0

上面的所有方法都可以正常工作,但是如果您将associative字符串作为键的数组该怎么办?

objectKeys = Object.keys;

arraySplit(arr, n) {
    let counter = 0;
    for (const a of this.objectKeys(arr)) {
        this.arr[(counter%n)][a] = arr[a];
        counter++;
    }
}

0

我有一个不会改变原始数组的

function splitArray(array = [], nPieces = 1){
    const splitArray = [];
    let atArrPos = 0;
    for(let i = 0; i < nPieces; i++){
        const splitArrayLength  = Math.ceil((array.length - atArrPos)/ (nPieces - i));
        splitArray.push([]);
        splitArray[i] = array.slice(atArrPos, splitArrayLength + atArrPos);
        atArrPos += splitArrayLength;
    }
    return  splitArray
}


0

您可以使用简单的递归函数

const chunkify = (limit, completeArray, finalArray = [])=>{
    if(!completeArray.length) return finalArray
    const a = completeArray.splice(0,limit);
    return chunkify(limit, completeArray, [...finalArray,a])
}


0
splitToChunks(arrayvar, parts) {
    let result = [];
    for (let i = parts; i > 0; i--) {
        result.push(arrayvar.splice(0, Math.ceil(arrayvar.length / i)));
    }
    return result;
}

0

一般来说,变异是Bad Thing™。

这是很好的,干净的和幂等的。

function partition(list = [], n = 1) {
  const isPositiveInteger = Number.isSafeInteger(n) && n > 0;
  if (!isPositiveInteger) {
    throw new RangeError('n must be a positive integer');
  }

  const partitions = [];
  const partitionLength = Math.ceil(list.length / n);

  for (let i = 0; i < list.length; i += partitionLength) {
    const partition = list.slice(i, i+partitionLength);
    partitions.push( partition );
  }

  return partitions;
}

-1

只需使用lodash的块函数将数组拆分为较小的数组https://lodash.com/docs#chunk不再需要弄乱循环了!


1
该问题询问如何使用Vannila js(而不是js库)解决此问题
TJ

3
感谢您提出来。我不知道lodash有这个。
Tiago

9
这也不能回答问题。他想要N个数组,而不是N个元素的数组。
mAadhaTTah

-3

如果您使用lodash,可以很容易地实现它,如下所示:

import {chunk} from 'lodash';
// divides the array into 2 sections
chunk([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 2); // => [[1,2,3,4,5,6], [7,8,9,10,11]]

3
错了 _.chunk创建N个元素的数组,而不是N个数组。您的示例的输出为6个数组,每个数组中的2个元素都为最后一个数组[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
Vassilis Barzokas,2016年

多数民众赞成在原来的问题是什么。请阅读问题中的预期行为。
Abhisekpaul
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.