105

I need to split a JavaScript array into n sized chunks.

E.g.: Given this array

["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"]

and a n equals to 4, the output should be this:

[ ["a1", "a2", "a3", "a4"],
  ["a5", "a6", "a7", "a8"],
  ["a9", "a10", "a11", "a12"],
  ["a13"]
]

I aware of pure JavaScript solutions for this problem, but since I am already using Lodash I am wondering if Lodash provides a better solution for this.

Edit:

I created a jsPerf test to check how much slower the underscore solution is.

Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69

5 Answers5

165

Take a look at lodash' chunk: https://lodash.com/docs#chunk

const data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
const chunks = _.chunk(data, 3);
console.log(chunks);
// [
//  ["a1", "a2", "a3"],
//  ["a4", "a5", "a6"],
//  ["a7", "a8", "a9"],
//  ["a10", "a11", "a12"],
//  ["a13"]
// ]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Edo
  • 3,311
  • 1
  • 24
  • 25
91

For Underscore based solution try this:

var data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
var n = 3;
var lists = _.groupBy(data, function(element, index){
  return Math.floor(index/n);
});
lists = _.toArray(lists); //Added this to convert the returned object to an array.
console.log(lists);

Using the chain wrapper method you can combine the two statements as below:

var data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
var n = 3;
var lists = _.chain(data).groupBy(function(element, index){
  return Math.floor(index/n);
}).toArray()
.value();
mindrones
  • 1,173
  • 11
  • 15
Chandu
  • 81,493
  • 19
  • 133
  • 134
  • 1
    Weirdly, Underscore's `groupBy` [returns an object](http://documentcloud.github.com/underscore/docs/underscore.html#section-31), not an array. Not a dealbreaker--you get basically the same functionality--but an odd bit of semantics. **Edit:** Oh, it does that to maintain chainability. Still interesting. – Jordan Running Dec 19 '11 at 20:09
  • @Cesar Canassa: For the sake of future developers who read that code, please consider putting that as a function in a custom library if you will use it a lot, or commenting it well if you won't :) – Briguy37 Dec 19 '11 at 20:12
  • @Jordan: Didn't realize that. Edited the answer to conver the object to array. – Chandu Dec 19 '11 at 20:14
  • While I'm all for using Underscore for the things it does best (and the OP asked for an Underscore-based solution so the **√** isn't undeserved) and for functional solutions, it seems like this solution introduces a ton of overhead. As @ajax33321 points out, `splice` in a loop is still a three-liner and, I'm guessing, way more performant. If your arrays are short, it probably doesn't matter, but if you're dealing with hundreds or thousands of items, do keep an eye on its performance. – Jordan Running Dec 19 '11 at 21:10
  • @Jordan I was already chaining various underscore functions when I hit this problem. Stoping the chain and running a splice loop didn't look good to me, that's why I went for the underscore solution. – Cesar Canassa Dec 19 '11 at 21:54
  • 1
    This is no longer the best solution. Please use chunk() instead – Jonah Aug 22 '16 at 05:11
7

Underscore supports _.chunk() natively as of version 1.9.0.

const data = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11", "a12", "a13"];
const chunks = _.chunk(data, 4);
console.log(chunks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>
Community
  • 1
  • 1
user9027325
  • 135
  • 2
  • 6
6

A possibly simpler expression:

const coll = [ "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9" ];
const n = 2;
const chunks = _.range(coll.length / n).map(i => coll.slice(i * n, (i + 1) * n));
console.log(chunks);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Artyom Ionash
  • 405
  • 7
  • 17
user1009908
  • 1,390
  • 13
  • 22
1

try this one it is much more practical (for example, if you would want to split the array based on amount of items to be container in each sub array):

function chunk(arr, start, amount){
    var result = [], 
        i, 
        start = start || 0, 
        amount = amount || 500, 
        len = arr.length;

    do {
        //console.log('appending ', start, '-', start + amount, 'of ', len, '.');
        result.push(arr.slice(start, start+amount));
        start += amount;

    } while (start< len);

    return result;
};

and the use in your case:

var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
    chunked = chunk(arr, 0, Math.floor(arr.length/3)); //to get 4 nested arrays

console.log(chunked);

and another case:

var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
    chunked = chunk(arr, 0, 3); // to get 6 nested arrays each containing maximum of 3 items

console.log(chunked);
  • That `start` parameter is not really practical imho. You usually would not want to pass it; but you always need to specify the chunk size. Better swap those two parameters. – Bergi Feb 07 '15 at 11:14
  • Why is this a `do while` loop? No one wants to get empty chunks. – Bergi Feb 07 '15 at 11:14