Home > Blockchain >  JS - how is [].concat(...arr) different from [...arr]?
JS - how is [].concat(...arr) different from [...arr]?

Time:10-22

I am going through JavaScript course on freecodecamp and I came across this 'Steamroller' challenge. Coming from python I really like one-liner solutions so I managed to write one for this challenge:

function steamrollArray(arr) {
  return Array.isArray(arr) ? [].concat(...arr.map(steamrollArray)) : arr;
}

steamrollArray([1, [2], [3, [[4]]]]) // returns [1, 2, 3, 4]

The goal is basically to flatten an array of arbitrary depth. What puzzles me though (still new to JavaScript) is why seemingly similar code behaves very differently. Something I wrote at the beginning doesn't work, but code I arrived at through trial and (mostly) error works:

[...arr.map(steamrollArray)] // this doesn't work, returns an unchanged array

[].concat(...arr.map(steamrollArray)) // this works, returns a flat array

This seems strange to me because 'unfolding' the recursion would suggest it should be the other way around

[...arr.map(steamrollArray)] 
[1, ...[2], ...[3, ...[...[4]]]] // this should work

[].concat(...arr.map(steamrollArray))
[].concat(...[1, [].concat(...[2]), [].concat(...[3, [].concat(...[].concat(...[4]))])]) // what 'is going on'

Can anyone please explain this behaviour?

CodePudding user response:

Quote from MDN:

Then, for each argument, its value will be concatenated into the array — for normal objects or primitives, the argument itself will become an element of the final array; for arrays ..., each element of the argument will be independently added to the final array.

You can observe this behavior below; both give us [1, 2, 3, 4, 5, 6], even though the second call uses an array.

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

If you want to concat the whole array, then you must wrap it in another array:

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

So how does this relate to your problem? Well... If we have an array like [1, 2, [3, 4], 5, [6]], and we spread that into concat, we would get something like this:

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

... essentially "flattening" it by one level. Your function is recursive, so it flattens the array like this until it is completely flat.

CodePudding user response:

Array.prototype.concat's arguments, if they are arrays, are essentially flattened into the resulting array. So

[2].concat(3, [4, 5])

results in [2, 3, 4, 5]. Similarly

[].concat(3, [4, 5])

results in [3, 4, 5].

But spreading an array into another array does not perform such flattening on each element of the spread array.

const arr = [3, [4, 5]];
const newArr = [...arr];

takes each element of arr and puts it into a new array - but that's it, giving you [3, [4, 5]] - the same values of the original array, but inside a different array.

For the code in your question:

[...arr.map(steamrollArray)]

fails because it's steamrollArray that does the flattening - but you'll also need to flatten each returned array. For example, if you pass in [[1, 2], [3, [4, 5]]]:

[...[[1, 2], [3, [4, 5]]].map(steamrollArray)]
[...[[1, 2], [3, 4, 5]]]

which doesn't flatten the top level. But [].concat(...arr.map(steamrollArray)) works because concat (shallowly) flattens each of its array arguments into the result.

CodePudding user response:

So others answer your question, but here are simplified code

const steamrollArray1 = arr => Array.isArray(arr) ? [].concat(...arr.map(steamrollArray1)) : arr;

const steamrollArray2 = arr => Array.isArray(arr) ? arr.flat(Infinity) : arr;

console.log(
  steamrollArray1([1, [2], [3, [[4]]]])
); // returns [1, 2, 3, 4]



console.log(
  steamrollArray2([1, [2], [3, [[4]]]])
); // returns [1, 2, 3, 4]

  • Related