DEV Community

Array Chunking

Ryan Farney on August 10, 2018

By array chunking, I mean taking an entire array and creating one array containing smaller subarrays of the original array's elements. Not only is ...
Collapse
 
ycmjason profile image
YCM Jason

Nothing beats a recursive solution when it comes to elegance IMO.

const chunk = (xs, n) => {
  if (xs.length <= n) return [xs];
  return [
    xs.slice(0, n),
    ...chunk(xs.slice(n), n)
  ];
};
Collapse
 
yever profile image
Ya'ar Hever

If you're already in the mood for elegance, why not use a single expression arrow function?

const chunk = (xs, n) => (xs.length <= n) ? [xs] : [
  xs.slice(0, n),
  ...chunk(xs.slice(n), n)
];
Collapse
 
ycmjason profile image
YCM Jason

because IMO that's not more elegant.

I think guard clauses should live in the if statement that has nothing to do with the rest of the code. I think it is easier to read.

But I believe there is no absolute "elegance". Programming is just like art, different people perceive a piece differently.

Thread Thread
 
moopet profile image
Ben Sinclair

Mm, Jason's version looks more readable to me. It's laid out a little more like you would if you were matching patterns in Haskell or something.

Collapse
 
kepta profile image
Kushan Joshi • Edited

Here are my 2 cents. It is performant and avoids recursion.

const chunk = (xs, n) => xs.reduce((o, x, i) => {
  o[Math.floor(i/n)].push(x);
  return o;
}, Array(Math.ceil(xs.length/n)).fill([]))
Collapse
 
ben profile image
Ben Halpern

It's funny where a practitioner's blindspots can grow. I literally wouldn't have known exactly what array chunking was. I could guess, but the specific named concept just was not part of my vocabulary.

Just goes to show how random and non-linear the software learning curve is.

Collapse
 
jreina profile image
Johnny Reina

This is generally how I do it if I need to partition and I don't have a utility library.

const splitEvery = (arr, n) => arr.reduce((memo, val) => {
  if(memo[memo.length - 1].length === n) memo.push([]);
  memo[memo.length - 1].push(val);
  return memo;
}, [[]]);
Collapse
 
basitali profile image
Basit Ali Mundia

Array.splice is a big no-no.

Collapse
 
ripaz profile image
Ripaz

Can you elaborate why is that?

Collapse
 
ycmjason profile image
YCM Jason

This is because Array.prototype.splice does whatever it does in place. Meaning that it mutates the original array. Mutation are usually consider to make code hard to reason about unless it is very explicit (like obj.setName('jason')).

Going back to the chunk example, consider the following code:

const arr = [1,2,3,4,5,6,7,8,9];
const chunks = chunk(arr, 3);

console.log(chunks);
console.log(arr);

What would you expect to be logged? I guess you would probably expect:

[[1,2,3], [4,5,6], [7,8,9]] // chunks
[1,2,3,4,5,6,7,8,9] // arr

But since Ryan suggested to use splice, it edits our arr in place. And the actual log you will see is:

[[1,2,3], [4,5,6], [7,8,9]] // chunks
[] // arr

A way to fix this is to clone the whole array before doing anything.

function chunk(array, size) {
  // clone the array to avoid touching the original one.
  array = array.slice(0)
  //declaring variable 'chunked' as an empty array
  let chunked = []

  //looping through the array until it has been entirely "manipulated" or split into our subarrays
  while(array.length > 0) {
    //taking the spliced segments completely out of our original array
    //pushing these subarrays into our new "chunked" array
    chunked.push(array.splice(0, size))
  }
  //returning the new array of subarrays
  return chunked
}

Or a more ES6+ method using array spread (preferable):

function chunk([...array], size) {
  //declaring variable 'chunked' as an empty array
  let chunked = []

  //looping through the array until it has been entirely "manipulated" or split into our subarrays
  while(array.length > 0) {
    //taking the spliced segments completely out of our original array
    //pushing these subarrays into our new "chunked" array
    chunked.push(array.splice(0, size))
  }
  //returning the new array of subarrays
  return chunked
}

I hope I explained this properly.

Thread Thread
 
basitali profile image
Basit Ali Mundia

Couldn't have explained it better.

Thread Thread
 
ryanfarney3 profile image
Ryan Farney

Thank you Basit and Jason for clearing this up! I certainly should have mentioned the dangers of mutating our original dataset.

Nonetheless, having knowledge of how to use .splice as well as it's dangers could show mastery of JS in the context of an interview <3

Collapse
 
schwarzwald profile image
schwarzwald

Or something like:

const chunk = (arr, n) => [...new Array(Math.ceil(arr.length / n))].map((x, i) => arr.slice(i * n, (i + 1) * n ))
Collapse
 
moopet profile image
Ben Sinclair

That's kind of hard to read (mostly because it's all on one loooong line).

Collapse
 
peralmq profile image
Pelle Almquist

Or even shorter 🤣

const chunk = ([...arr], n) => [...Array(Math.ceil(arr.length) / n)].map(_ => arr.splice(0, n))

Collapse
 
mshertzberg profile image
Michael Scott Hertzberg

another simple loop version

  const toChunks = (arr, chunkLength) => {
    const chunks = []
    let i, j, chunk
    for (i = 0, j = arr.length; i < j; i += chunkLength) {
      chunk = arr.slice(i, i + chunkLength)
      chunks.push(chunk)
    }
    return chunks
  }
Collapse
 
gregorys100 profile image
Gregory Sequeira

Learned something new today. ! ~_~

But I am unable to see the use case for this. Why would I need to do chunking in the first place?

Collapse
 
seahindeniz profile image
Sahin D.

When you need to fetch something from a 3rd party server and that server only accepts 1000 ids.. So you may need chunking for your 5000 id numbers :)