I needed to "convert" a simple flat array into a 2D array and I went on SO to see what it has to say about the argument.
I tried to recreate the code of this answer, and I got this error:
console.log(array.reduce((twoDArray, n, i) => (i % 3 == 0 ? twoDArray.push([n]) : twoDArray[twoDArray.length-1].push(n)), []));
^
TypeError: Cannot read property 'push' of undefined
The problem was that I didn't add && twoDArray
at the end of the arrow function. Here you can see:
let array = [1,2,3,4,5,6,7,8,9];
// this works
console.log(array.reduce((twoDArray, n, i) => (i % 3 == 0 ? twoDArray.push([n]) : twoDArray[twoDArray.length-1].push(n)) && twoDArray, []));
// here the second push() throws an error
console.log(array.reduce((twoDArray, n, i) => (i % 3 == 0 ? twoDArray.push([n]) : twoDArray[twoDArray.length-1].push(n)), []));
Now I don't understand a couple of things, namely:
&& twoDArray
works? what's its purpose?push()
that generates the error. Shouldn't the code throw an error before to reach the &&
?This is needed because push
returns the new length of the array - but the accumulator needs to be the array, not the length.
Without the &&
, and indenting the code into multiple lines to make it clearer what's going on, the second code is equivalent to:
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// here the second push() throws an error
console.log(array.reduce((twoDArray, n, i) => {
return (i % 3 == 0 ? twoDArray.push([n]) : twoDArray[twoDArray.length - 1].push(n))
}, []));
Same as:
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// here the second push() throws an error
console.log(array.reduce((twoDArray, n, i) => {
return (
i % 3 == 0
? twoDArray.push([n])
: twoDArray[twoDArray.length - 1].push(n)
);
}, []));
Now, the problem should be clear: no matter which condition is entered, the callback evaluates to
return (
i % 3 == 0
? someNumber
: someNumber
);
because .push
evaluates to the new length of the array.
Adding && twoDArray
to it makes the callback look like:
return (
i % 3 == 0
? someNumber
: someNumber
) && twoDArray;
therefore returning twoDArray
instead of the number.
Shouldn't the code throw an error before to reach the &&?
It does. The error is thrown on the second iteration, when twoDArray[twoDArray.length-1]
, when twoDArray
is a number, evaluates to undefined
, so it can't be pushed to. But the problem that twoDArray
is a number instead of an array results from the code at the tail end of the prior (first) iteration: the lack of the && twoDArray;
.
Code like this is extremely confusing. Try not to condense code into a single line if it makes it unreadable. Another issue is that .reduce
arguably isn't appropriate when the accumulator is the same object on each iteration. Consider instead doing something like this:
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const twoDArray= [];
array.forEach((n, i) => {
i % 3 == 0
? twoDArray.push([n])
: twoDArray[twoDArray.length - 1].push(n);
});
console.log(twoDArray);
And use if
/else
instead of the conditional operator:
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const twoDArray= [];
array.forEach((n, i) => {
if (i % 3 === 0) twoDArray.push([n])
else twoDArray[twoDArray.length - 1].push(n);
});
console.log(twoDArray);