【问题标题】:Splitting a JS array into N arrays将一个 JS 数组拆分为 N 个数组
【发布时间】:2012-01-01 13:53:23
【问题描述】:

想象一下我有一个这样的 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]]],我决定用空分隔符来做。

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/

你会怎么做?谢谢!

【问题讨论】:

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

标签: javascript arrays


【解决方案1】:

您可以使切片“平衡”(子数组的长度差异尽可能小)或“均匀”(所有子数组,但最后一个子数组的长度相同):

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>

【讨论】:

  • 你的解决方案很简洁,它和我的递归解决方案做的一样,但没有那么乱。谢谢!
  • 像魅力一样工作.. 不错的解决方案
  • 嗨@georg,你能解释一下这行吗:var size = Math.ceil((len - i) / n--);
  • 嗨@georg 谢谢。我将如何修改此代码以确保所有子数组的长度相同,除了最后一个子数组(当然,除非除数没有余数,因此所有子数组都相等)。感谢您的帮助。
  • @cbdeveloper: 给你function chunkify&lt;T&gt;(a: T[], n: number, balanced: boolean): T[][]
【解决方案2】:

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

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 确保在元素数量不均匀的情况下,它们将进入最早的块。

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

【讨论】:

  • 这个解决方案对我有用。只有一个建议。为了不破坏初始数组,添加此行const copyArray = array.map(v =&gt; v); 以创建数组的浅表副本。然后在函数的其余部分操作复制的数组。
  • 稍作修改。它还将创建一个副本。 const copyArray = [...array]
【解决方案3】:

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

【讨论】:

  • 这在 n = 5 和 arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 时无法正常工作。
  • 这不会拆分为 n 个子数组,而只是拆分为 n 个长度的子数组。
  • 请添加一些解释为什么此代码有助于 OP。这将有助于为未来的观众提供一个可以学习的答案。请参阅How to Answer 了解更多信息。
  • 解决 OPs 问题,你用 split(arr, Math.ceil(arr.length/chunkCount)) 我猜...但是我来这里是为了寻找拆分成 N 大小的块所以这非常适合:)
【解决方案4】:

我刚刚对算法做了一个迭代实现: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;
}

【讨论】:

  • (按预期工作,但我只能选择一个答案作为正确答案)嗨!感谢您的帮助。很好地思考如何使用其余部分。
【解决方案5】:

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

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

希望对你有帮助!

编辑:因为有些人仍在评论这并不能回答问题,因为我正在修复 每个块的大小而不是我想要的块数。这里是解释我在 cmets 部分中试图解释的代码:使用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>') }

【讨论】:

  • 哇,这个方法很简洁!爱它!做得好! :-)
  • 我喜欢这种技术,但它并没有回答问题。它返回任意数量的 x 大小的块,而问题是要求 x 数量的均匀大小的块。
  • 喜欢这个!!!我已经重构以返回大小均匀的块 function splitArr(arr, n) { return arr.reduce(function (a, i) { if (a[a.length - 1].length &gt;= arr.length / n) { a.push([]) } a[a.length - 1].push(i) return a; }, [[]]) }
  • 肯定没有回答问题。
  • 简洁且非常聪明,这是我用这种简单模式解决这个问题和其他所有情况的首选方法,谢谢!
【解决方案6】:

更新:2020 年 7 月 21 日

我几年前给出的答案只有在originalArray.length numCols 时才有效。您也可以在下面使用类似这个函数的东西,但这会创建一个与手头的问题不太匹配的布局(水平排序而不是垂直排序)。又名:[1,2,3,4] -> [[1,4],[2],[3]]。我知道这可能仍然有价值,所以我将把它留在这里,但我推荐Senthe's answer

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 numCols)也可以保证 n 列)

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 ],[]]。
  • 你是绝对正确的@PratikKulshreshth。我会更新答案。对于任何感兴趣的人,我现在最喜欢 Senthe 的回答:stackoverflow.com/a/51514813/1322810
【解决方案7】:

一般来说,突变是一件坏事™。

这很好,干净且幂等。

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

[编辑添加]

这是另一种风格,调用者指定分区大小而不是要创建的分区数量:

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 = [];

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

  return partitions;
}

如果你希望它是“平衡的”,使得各个块的长度差异不超过 1,那只需要一点数学。

要以这种方式将 M 个东西分配到 N 个桶中,我们首先需要确定 M / N 的商 Q 和余数 R。

令 Q 表示基本分区长度。 R 将始终小于 N,并且是需要在所有分区中分配的多余项目的数量。因此,前 R 个分区将包含 Q+1 个项目,其余分区将包含 Q 个项目。

例如,要将 100 个项目的列表划分为 8 个桶,我们得到:

M = 10 N = 8 Q = 12 R = 4

所以我们会得到:

  • 4 (R) 桶 Q+1 (13) 项,以及
  • 4 (N-R) 桶 Q (12) 个项目

然后 4 * 13 + 4 * 12 减少到 52+48,即 100。

这导致我们这样做:

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

  const q = Math.floor( list.length / n );
  const r = list.length % n;

  let i   ; // denotes the offset of the start of the slice
  let j   ; // denotes the zero-relative partition number
  let len ; // denotes the computed length of the slice

  const partitions = [];
  for ( i=0, j=0, len=0; i < list.length; i+=len, ++j ) {
    len = j < r ? q+1 : q ;
    const partition = list.slice( i, i+len ) ; 
    partitions.push( partition ) ;
  }

  return partitions;
}

【讨论】:

  • 你的代码很棒!我们需要传递我们需要将数组分成多少块,但是指定每个块需要多少元素的方法需要由我们自己计算,即partition(arr, Math.round(arr.length / n)) 其中arr 是实际数组,n 是每个块允许的最大元素数。
【解决方案8】:

如果你碰巧事先知道你想要的块的大小,有一个非常优雅的 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]));

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

【讨论】:

  • 除非这对于 n groupsOfFour( [ 1 ] ) 返回 [ 1 , undefined, undefined, undefined ],而不是预期的(和期望的)[ [1] ]
【解决方案9】:

递归方法,未经测试。

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

【讨论】:

    【解决方案10】:

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

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

    【讨论】:

      【解决方案11】:

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

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

      我已经创建了我自己的要分块的数组。你可以找到代码here

      我们在 lodash 库中还有一个方法“chunk”,它非常有用。希望有帮助

      【讨论】:

        【解决方案12】:
        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;
            }
        

        【讨论】:

          【解决方案13】:
          splitToChunks(arrayvar, parts) {
              let result = [];
              for (let i = parts; i > 0; i--) {
                  result.push(arrayvar.splice(0, Math.ceil(arrayvar.length / i)));
              }
              return result;
          }
          

          【讨论】:

            【解决方案14】:

            我是这样弄的,效果很好……

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

            【讨论】:

              【解决方案15】:

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

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

              【讨论】:

                【解决方案16】:

                如果你知道想要设置 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]

                【讨论】:

                  【解决方案17】:

                  只需使用 lodash 的 chunk 函数将数组拆分为更小的数组 https://lodash.com/docs#chunk 无需再摆弄循环!

                  【讨论】:

                  • 问题询问如何使用vannila js解决这个问题,而不是使用js库
                  • 感谢您提出这个问题。我不知道 lodash 有这个。
                  • 这也没有回答问题。他想要 N 个数组,而不是 N 个元素的数组。
                  【解决方案18】:

                  如果您可以使用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
                  } 
                  

                  【讨论】:

                    【解决方案19】:

                    以上所有方法都可以正常工作,但是如果您有 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++;
                        }
                    }
                    

                    【讨论】:

                      【解决方案20】:

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

                      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
                      }

                      【讨论】:

                        【解决方案21】:

                        你可以使用一个简单的递归函数

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

                        【讨论】:

                          【解决方案22】:

                          如果你使用 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]]
                          

                          【讨论】:

                          • 这是错误的。 _.chunk 创建 N 个元素的数组而不是 N 个数组。您的示例将输出 6 个数组,每个数组有 2 个元素,除最后一个 [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
                          • 这就是最初的问题。请阅读问题中的预期行为。
                          猜你喜欢
                          • 2012-01-01
                          • 2013-03-12
                          • 2017-03-03
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多