120

Imagine I have an JS array like this:

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

What I want is to split that array into N smaller arrays. For instance:

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]]

For Python, I have this:

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

For JS, the best right solution that I could come up with is a recursive function, but I don't like it because it's complicated and ugly. This inner function returns an array like this [1, 2, 3, null, 4, 5, 6, null, 7, 8], and then I have to loop it again and split it manually. (My first attempt was returning this: [1, 2, 3, [4, 5, 6, [7, 8, 9]]], and I decided to do it with the null separator).

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

Here's a jsfiddle of that: http://jsfiddle.net/uduhH/

How would you do that? Thanks!

Tiago
  • 9,457
  • 5
  • 39
  • 35
  • 2
    related to - http://stackoverflow.com/q/40166199/104380 – vsync Oct 21 '16 at 08:11
  • 1
    Your `split` function is not far off. You can remove the `null` business by adding two array wrappers: `if (cols == 1) return [array]` and `return [array.slice(0, size)].concat(split(array.slice(size), cols-1))`. I find this recursive version much more readable than most of the answers here. – Scott Sauyet Oct 03 '18 at 18:41

24 Answers24

158

You can make the slices "balanced" (subarrays' lengths differ as less as possible) or "even" (all subarrays but the last have the same length):

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>
mgilson
  • 300,191
  • 65
  • 633
  • 696
georg
  • 211,518
  • 52
  • 313
  • 390
  • Your solution is neat, it does same thing that my recursive solution does but without all that mess. Thank you! – Tiago Nov 19 '11 at 12:32
  • Hi @georg, can you please explain this line : `var size = Math.ceil((len - i) / n--);` – dpg5000 Feb 11 '16 at 06:00
  • @dpg5000: when slicing the next chunk, its size is the number of remaining elements (`len - i`) by the number of remaining chunks (`n--`) – georg Feb 11 '16 at 06:28
  • 1
    Hi @georg thank you. How would I modify this code to ensure that all sub-arrays are of equal length except for the last subarray (unless of course the divisor results in no remainder, whereby all subarrays would be equal). I appreciate any help. – dpg5000 Feb 17 '16 at 00:59
  • Can we get a Tyescript version? – cbdeveloper May 31 '21 at 16:05
  • 1
    @cbdeveloper: here you go `function chunkify(a: T[], n: number, balanced: boolean): T[][]` – georg May 31 '21 at 17:50
62

I think this way using splice is the cleanest:

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

// Example:

const example = [0,1,2,3,4,5,6,7,8,9,10,11,12]

console.log(splitToNChunks([...example], 3))
console.log(splitToNChunks([...example], 5))

For example, for n = 3, you would take 1/3, then 1/2 of the remaining part, then the rest of the array. Math.ceil ensures that in case of uneven number of elements they will go to the earliest chunks.

(Note: calling .splice on an array will directly change its length. To avoid destroying your initial array, you can use its temporary shallow copy instead: const copiedArray = [ ...originalArray ])

JoannaFalkowska
  • 2,771
  • 20
  • 37
  • 2
    This solution worked for me. Just one suggestion. In order to not destroy the initial array, add this line `const copyArray = array.map(v => v);` to create a shallow copy of the array. Then manipulate the copied array in the rest of the function instead. – Peet Jul 28 '21 at 17:11
  • A little modification. It would also create a copy. ``const copyArray = [...array]`` – PCPbiscuit Nov 01 '21 at 17:26
19

function split(array, n) {
  let [...arr]  = array;
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, n));
  }
  return res;
}
Mohan Narayanaswamy
  • 2,149
  • 6
  • 33
  • 40
bagbee
  • 355
  • 2
  • 3
  • 2
    This does not works as expected for n = 5 and arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]. – Tiago Sep 27 '14 at 16:51
  • 1
    this doesn't split to n subarrays, merely to subarrays of n length. – dpg5000 Feb 17 '16 at 01:28
  • 1
    Please add some explanation of why this code helps the OP. This will help provide an answer future viewers can learn from. See [answer] for more information. – Heretic Monkey Oct 21 '16 at 21:49
  • 1
    to solve the OPs question you use this with split(arr, Math.ceil(arr.length/chunkCount)) I guess... but I came here looking for splits into N-sized chunks so this fit perfectly :) – wizzard0 Jun 16 '21 at 07:46
15

I just made an iterative implementation of the algorithm: http://jsfiddle.net/ht22q/. It passes your test cases.

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;
}
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 1
    (This works as expected, but I can only choose one answer as correct) Hi! Thanks for helping. Nice thinking about how to use the rest. – Tiago Nov 19 '11 at 12:34
10

You can reduce it into a matrix. The example below split the array (arr) into a matrix of two-positions arrays. If you want other sizes just change the 2 value on the second line:

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

Hope it helps!

EDIT: Because some people is still commenting this doesn't answer the question since I was fixing the size of each chunk instead of the number of chunks I want. Here it comes the code explaining what I'm trying to explain in the comments section: Using the 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>') }
sospedra
  • 14,238
  • 3
  • 21
  • 32
8

Update: 7/21/2020

The answer I've given a few years ago only works if originalArray.length <= numCols. You could alternatively use something like this function below, but that will create a layout that doesn't quite match the question at hand (horizontal sorting rather than vertical sorting). AKA: [1,2,3,4] -> [[1,4],[2],[3]]. I understand this might still provide value so I'll leave this here, but I recommend 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
}

Original Answer from 2017:

Old question, but since vanillaJS is not a requirement and so many are trying to solve this with lodash/chunk, and without mistaking what _.chunk actually does, here's a concise + accurate solution using lodash:

(Unlike the accepted answer, this also guarantees n columns even if originalArray.length < 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
}

The for loop at the end is what guarantees the desired number of "columns".

Joao
  • 2,696
  • 2
  • 25
  • 35
  • This fails when array length is 4 and numCols is 3. Tried with splitArray([1, 2, 3, 4], 3) and it returns [[1, 2], [3, 4], []]. – Pratik Kulshreshth Jul 21 '20 at 11:37
  • You're absolutely right @PratikKulshreshth. I'll update the answer. For any interested, I like Senthe's answer the best now: https://stackoverflow.com/a/51514813/1322810 – Joao Jul 21 '20 at 21:50
7

Mutation is, generally speaking, a Bad Thing™.

This is nice, clean, and idempotent.

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

[Edited to add]

Here's another flavor where the caller specifies the partition size rather than the number of partitions to be created:

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

And if you want that to be "balanced" such that the individual chunks will differ in length by no more than 1, that only requires a little math.

To distribute, say M things into N buckets in that manner, we need to first determine the quotient Q and remainder R of M / N.

Let Q denote the basic partition length. R will always be less than N, and is the number of excess items that need to be distributed across all the partitions. Ergo, the first R partitions will contain Q+1 items and the remaining partitions will contain Q items.

For example, to partition a list of 100 items into 8 buckets, we get:

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

So we will get:

  • 4 (R) buckets of Q+1 (13) items, and
  • 4 (N-R) buckets of Q (12) items

And 4 * 13 + 4 * 12 reduces to 52+48, or 100.

That leads us to this:

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;
}
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • Your code is great! We need to pass how many chunks we need to divide our array into but the way to specify how many elements we need per chunk needs to be calculated by our self I.e. `partition(arr, Math.round(arr.length / n))` where `arr` is the actual array and `n` is the max number of elements allowed per cunk. – Azzaz Jul 09 '21 at 06:23
6

If you happen to know the size of the chunks you want beforehand, there's a pretty elegant ES6 way of doing this:

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

I find this notation pretty useful for, for example parsing RGBA out of a Uint8ClampedArray.

user1034533
  • 1,054
  • 9
  • 9
  • 1
    Except that this fails for n < 4: `groupsOfFour( [ 1 ] )` returns `[ 1 , undefined, undefined, undefined ]`, rather than the expected (and desired) `[ [1] ]`. – Nicholas Carey Sep 16 '20 at 23:35
  • you want `groupsOfFour` to return a group of one? might have the wrong function – user1034533 Feb 03 '22 at 03:12
3

Another recursive works quite well, it is less ugly

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);
}
Mcs
  • 534
  • 1
  • 5
  • 14
3
function splitArray(arr, numOfParts = 10){
        const splitedArray = []
        for (let i = 0; i < numOfParts;i++) {
            const numOfItemsToSplice = arr.length / numOfParts;
            splitedArray.push(arr.splice(0, numOfItemsToSplice))
        }
        return splitedArray;
    }
Daher
  • 1,001
  • 10
  • 10
Stefdelec
  • 2,711
  • 3
  • 33
  • 40
2

Partition

const partition = (x,n) => {
  const p=x.length%n, q=Math.ceil(x.length/n), r=Math.floor(x.length/n);
  return [...Array(n)].reduce((a,_,i)=>(a[0].push(x.slice(a[1],(a[1]+=i<p?q:r))),a),[[],0])[0];
};

DEMO

// to make it consistent to filter pass index and array as arguments
const partition = (x,n) => {
    const p = x.length % n,q = Math.ceil(x.length / n),r = Math.floor(x.length / n);
    return [...Array(n)].reduce((a,_,i)=>(a[0].push(x.slice(a[1],(a[1]+=i<p?q:r))),a),[[],0])[0];
};

console.log(partition([], 3))
console.log(partition([1, 2], 3))
console.log(partition([1, 2, 3, 4, 5, 6, 7, 8, 9], 3))
console.log(partition([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3))

For Typescript

const partition = <T>(x: T[], n: number) => {
    const p = x.length % n, q = Math.ceil(x.length / n), r = Math.floor(x.length / n);
    return [...Array(n) as never[]].reduce((a, _, i) =>
        (a[0].push(x.slice(a[1], a[1] += i < p ? q : r)), a)
        , [[], 0] as [T[][], number])[0]
}

ONE-LINER Partition (but different order)

const part=(x,n)=>x.reduce((a,v,i)=>(a[i%n].push(v),a),[...Array(n)].map(()=>[]));

DEMO

// to make it consistent to filter pass index and array as arguments
const part=(x,n)=>x.reduce((a,v,i)=>(a[i%n].push(v),a),[...Array(n)].map(()=>[]));

console.log(part([1, 2, 3, 4, 5], 3));
console.log(part([1, 2, 3, 4, 5, 6], 3));
console.log(part([1, 2], 3));

For Typescript

const part = <T>(array: T[], parts: number) =>
  array.reduce(
    (acc, value, i) => (acc[i % parts].push(value), acc),
    [...Array(parts)].map(() => []) as T[][]
  );
nkitku
  • 4,779
  • 1
  • 31
  • 27
2

Recursive approach, not tested.

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);
    }
}
12000
  • 103
  • 2
1

Just use lodash' chunk function to split the array into smaller arrays https://lodash.com/docs#chunk No need to fiddle with the loops anymore!

1

Probably the cleaner approach would be the following (without using any other library) :

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

I have created my own array which is to be chunked. You can find the code here

Also we have a method "chunk" in the lodash library which is of great use. Hope that helps

Vaibhav Pachauri
  • 2,671
  • 2
  • 19
  • 32
0

I made it this way, it works...

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;
}
juanmorschrott
  • 573
  • 5
  • 25
0

check my version of this array split

// 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;
};
hitesh upadhyay
  • 264
  • 1
  • 9
0

if you know wanna set child_arrays.length then i think this solution best:

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

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

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

Anja Ishmukhametova
  • 1,535
  • 16
  • 14
0

If you can use lodash and would like a functional programming approach, here is what I come up with:

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
} 
Hiep Le
  • 381
  • 1
  • 2
  • 12
0

all above might work fine, but what if you have associative array with strings as keys?

objectKeys = Object.keys;

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

I have one that doesn't alter original array

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
}
Ihsan Müjdeci
  • 914
  • 1
  • 9
  • 14
0

You can use a simple recursive function

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;
}
volvereabhi
  • 50
  • 13
0

There's a library called PartitionJS that does exactly this (full disclosure, I wrote it). It will partition an array into as many partition as you specify.

const data = [12, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1]
const [partitionTwo1, partitionTwo2] = partition().divide(data, 2)
const [partitionThree1, partitionThree2, partitionThree3] = partition().divide(data, 3);

Will result in this

partitionTwo1 => [12, 2, 3, 4, 5, 6]
partitionTwo2 => [7, 8, 9, 10, 11, 1]

partitionThree1 => [12, 2, 3, 4]
partitionThree2 => [5, 6, 7, 8]
partitionThree3 => [9, 10, 11, 1]

If the divide method is not partitioning the array exactly like you want it to there are additional method to have full control over what goes into each partition by registering callbacks.

const nums = [1, 2, 2, 4, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

const splitSum = partition()
    .add(i => i < 6)
    .add(i => i > 5 && i < 11)
    .add(i => i > 10 && i < 14)
    .split(nums);

splitSum => [
    [1, 2, 2, 4, 1, 3, 4, 5],
    [6, 7, 8, 9, 10],
    [11, 12],
]

As an added bonus you can also partition arrays asynchronously by spawning a web worker (or web thread in Node), that will partition the array on a separate thread so it doesn't block execution.

const reallyBigArray = [1, ... , 1000000]

console.log('--- start ---');

partition()
    .async()
    .add(i => i < 33)
    .add(i => i > 32 && i < 66)
    .add(i => i > 67)
    .split(reallyBigArray)
    .then(result => {
      console.log('Partitions done processing');
    });

console.log('--- UI element loaded ---');

'--- start ---'
'--- UI element loaded ---'
'Partitions done processing'
moult86
  • 73
  • 1
  • 6
-3

If you are using lodash, you can achieve it fairly easily like below:

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]]
abhisekpaul
  • 497
  • 7
  • 5
  • 3
    This is wrong. `_.chunk` creates arrays of N elements not N arrays. Your example would have an output of 6 arrays with 2 elements in each exept the last one ```[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]``` – Vassilis Barzokas Dec 23 '16 at 11:00
  • 1
    thats what the original question is. please read the expected behaviour in the question. – abhisekpaul Mar 14 '17 at 13:38