Matthew Player
Matthew Player

Reputation: 338

Recursively flattening an array (why is my function not working?)

So I am trying to get my head around recursion and recently came across a problem which was easiest solved using recursion anyway. I am trying to flatten an array of varying depth arrays and the function I cam up with to do this was:

mixedArray = [1, 2, [3, 4], [[5, 6], 7], 8, 9];

function flattenArray(array) {
	return array.map(val => {
		if (typeof val !== "number") {
			return flattenArray(val);
		} else {
			return val;
		}
	});
}

console.log(flattenArray(mixedArray));

Now, I am expecting this to output [1,2,3,4,5,6,7,8,9] but it just returns mixedArray as is. My thinking was that it takes the array and (assuming that the array is not just a single number) maps through the entries. If an entry is a single number then it should return that number if it is not, then it should call the original function on this entry, and so on and so forth. Hopefully this is something fairly basic like a syntax error (which would be fairly typical!!)

Please note; I would like to know WHY my function is not working as well as looking at a solution.

Thanks (in advance)

Upvotes: 0

Views: 57

Answers (3)

trincot
trincot

Reputation: 350252

You cannot do that with a plain map method, as that will just return the same number of elements as in the array the map is applied on (i.e., the top level).

Instead use reduce, and then make sure to extend what you already have with whatever must be added to it:

mixedArray = [1, 2, [3, 4], [[5, 6], 7], 8, 9];

function flattenArray(array) {
    return array.reduce( (flat, val) => {
        if (typeof val !== "number") {
            return [...flat, ...flattenArray(val)];
        } else {
            return [...flat, val];
        }
    }, []);
}

console.log(flattenArray(mixedArray));

You can use a ternary operator instead of if ... else to condense the code a bit:

mixedArray = [1, 2, [3, 4], [[5, 6], 7], 8, 9];

const flattenArray = array =>
    array.reduce( (flat, val) => 
        [...flat, ...(Array.isArray(val) ? flattenArray(val) : [val])]
    , []);

console.log(flattenArray(mixedArray));

... and of course, we should mention the ESNext proposal Array#flat, which just does all that in one method call.

mixedArray = [1, 2, [3, 4], [[5, 6], 7], 8, 9];

console.log(mixedArray.flat(Infinity));

Upvotes: 5

Shidersz
Shidersz

Reputation: 17190

I will modify it into something like this. Because you actually are mapping all elements to the same elements:

mixedArray = [1, 2, [3, 4], [[5, 6], 7], 8, 9];

function flattenArray(array, out=[])
{
    array.forEach(val =>
    {
        if (typeof val !== "number")
            out = out.concat(flattenArray(val));
        else
            out.push(val);
    });
    
    return out;
}

console.log(flattenArray(mixedArray));

Upvotes: 1

Allain Lalonde
Allain Lalonde

Reputation: 93348

The issue is that mapping an array to an array doesn't change it, it's still an array in the resulting array:


function flattenArray(array) {
  return array.reduce((result, x) => result.concat(
    Array.isArray(x) ? flattenArray(x) : x), [])
}

That should do the same but uses reduce rather than map.

Upvotes: 2

Related Questions