2

Im trying to split my array of 9 elements into array of 3 arrays, each with 3 elements.

const data = [{
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
];

const chunks = Array(3).fill(Array());
data.forEach((value, index) => chunks[index % chunks.length].push(value));
console.log(chunks);

I can't understand why it's producing array of 3 arrays with 9 elements in each.

Whats is wrong with chunks[index%chunks.length].push(value)?

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
gib
  • 738
  • 1
  • 8
  • 16

5 Answers5

4

This happens because fill is returning the same array to each element, hence pushing to either of the three is, in fact, pushing to the same reference.

Mentioned in the MDN docs:

The fill() method fills (modifies) all the elements of an array from a start index (default zero) to an end index (default array length) with a static value. It returns the modified array.

^-- a STATIC value. Read more about fill here

To make it clearer, this:

const chunks = Array(3).fill(Array());

can be "interpreted" as this:

const chunks = new Array(3);
var arr = new Array();
for (var i = 0; i < chunks.length; i++) chunks.push(arr);

So, basically, the same reference of the array is pushed to the original array; hence, altering arr means altering all the references of it, so all the references in chunks are pointing to the same array, so all are "altered", meaning they all will point to the same array and share the same values.

You can solve that in many ways, the fastest I can think of is using Array.from as done below, where the callback returns a new array everytime.

const data = [
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
  {
    name: 'a',
    surname: 'b'
  },
];

const chunks = Array.from({length: 3}, (_) => []);
data.forEach((value, index) => chunks[index%chunks.length].push(value));
console.log(chunks);

Relevant code explanation

Array.from({length: 3}, (_) => []);

  • Array.from creates a new array from the defined properties list. in this case, it creates a new array with length 3.
  • (_) => [] is the callback invoked to assign the value of each element of the array. In that case, I'm just returning a new array. This could also be shortened to: () => []

And that's it.

briosheje
  • 7,356
  • 2
  • 32
  • 54
4

Just in case, you might be interested in solution that may work 3 times faster, check out my approach:

const data=[{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},{name:'a',surname:'b'},];

const chunkArr = (arr, size) => arr.reduceRight((r,i,_,s) => (r.push(s.splice(0,size)),r),[]);

console.log(chunkArr(data,3));
.as-console-wrapper {min-height: 100%}
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
1

You could slice the array by the index and the wanted size.

var data = [{ name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }],
    chunks = [],
    size = 3,
    i = 0;
    
while (i < data.length) chunks.push(data.slice(i, i += size));

console.log(chunks);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1
const chunks = Array(3).fill(Array());

You have filled the chunks array with 3 times the same array object. Spelled out:

const chunk = Array();
const chunks = Array(3).fill(chunk);

Which basically ends up being called in forEach:

data.forEach((value, index) => chunk.push(value));

Change it to:

const chunks = Array(3).fill(1).map(() => []);
Jan
  • 1,807
  • 13
  • 26
0

For me, a more visual solution is using the Array.from() method:

const arr = [{ name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }, { name: 'a', surname: 'b' }];
    
const resulr = Array.from({length: 3}, (_, i) => arr.slice(i * 3, (i + 1) * 3));

// Or a common solution:

const chunk = (arr, l) => Array.from({length: Math.ceil(arr.length/l)}, (_, i) => arr.slice(i * l, (i + 1) * l));

console.log(resulr);
console.log(chunk(arr, 3));
Alex
  • 1,457
  • 1
  • 13
  • 26