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]