Learn to Flatten and Flatmap Arrays with Reduce

Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 5 years ago

Learn a few advanced reduction patterns: flatten allows you to merge a set of arrays into a single array, the dreaded flatmap allows you to convert an array of objects into an array of arrays which then get flattened, and reduceRight allows you to invert the order in which your reducer is applied to your input values.

[00:00] Let's talk about a couple of patterns for reduction that you might not necessarily immediately think, "Oh, that's a place I should use reduce." A really common one is called flatten. Let's say you have an array of arrays.

[00:16] Maybe you've got one, two, three, four, five, six, seven, eight, nine. It's just like an accident of the way your code is structured that all of these values are stored into all these different arrays. You just care about these values. You wish instead of an array of three arrays you just had an array of nine numbers.

[00:38] Well, great news. We can use reduce to flatten this array into a single array. Flatten is what you call it when you take a list of lists of some thing and you return just a single list of that thing. That's going to work like this. Take your accumulator, take your value, and we're going to merge all of this stuff into a single new array.

[01:12] There's a bunch of ways you could accomplish this. All we're going to do is we're just going to say return accumulator.concat value. Now if we log this out you'll see that we have a single list of all those values.

[01:32] Now some of you might be saying, "OK, that's well and good. You've got this little-contrived example here of a list of values inside of a list," so we've got nested lists. In the real world, it rarely gets this simple. What this is actually going to be is some giant list of domain objects, and you're going to need to transform this and pull this out and go through all these steps.

[01:55] You can't take some bunch of really complex objects and just reduce that down to an array of just the things you care about. That kind of thing only works in examples in tutorial videos. How could you do something like that with more complex data?

[02:09] I'd like to introduce you to my next pattern here. Instead of flattening a list of lists, what we're going to do is take a list of complex objects, in this case it's a list of movies. Christopher Nolan's Batman movies.

[02:26] What we're going to do is we're going to reduce this list of objects into a single list of the stars that appeared in these movies. We want to make sure among other things that only include each star once. We don't want to see Christian Bale showing up three times.

[02:48] How can we do this with a single reduce operation? Well, this kind of operation has a special name. This is called a flat map. If that sounds familiar, it might be because at your local meetup you might have heard some functional programmers talking about monads and flat maps and the rest of us have no idea what they're talking about, right?

[03:07] Well, until today, because I'm going to tell you of course that a flat map is actually a type of reduce operation like map is a reduce, like filter's a reduce. A flat map takes a list of values here, where those values are potentially complex objects, and it turns each of those values into an array, somehow.

[03:27] In this case what we're going to do is we're going to step through and we're going to, for each of these movies, we're just going to pull out the cast. Then it concatenates those arrays together with the flatten operation and what you have in the end is a list of values that were extracted through some compound operation from a list of more complex values.

[03:49] The naïve way to do this would be to save our stars equals input.reduce accumulator value. Our initial accumulator state is going to be an empty array. Like I said, the naïve way to do this would just be to say return accumulator.concat value.cast.

[04:13] But this doesn't satisfy one of our constraints. If we say console.log stars, OK, there's all our stars, but Christian Bale, Christian Bale, Christian Bale. That's a lot of Christian Bale.

[04:30] Can we do this better? Of course we can because this is a reduce operation. This isn't some...It's not like map where we are constrained in terms of what we can return. We've got a lot of power. We've got a lot of flexibility.

[04:43] We can do something like this. We can say value.cast.for each function star. We can say, if act.index of star is greater than negative one, then...Let's invert that. If the accumulator does not have an instance of that star, if that star is not yet in the accumulator, let's say act.push star. Then return the accumulator, and bam. There you go. We've been able to reduce this list of movies through a flat map into a list of movie stars.

[05:29] There's one more reduce pattern that I'd like to show you. This is not one that I think I've ever had occasion to use, but I'm sure somebody out there can get a lot of mileage out of this for exactly the right use case, and so it's a tool worth having in your toolbox.

[05:46] As you know, if you reduce over a set of values. Let's create some dummy data here. I'm going to make five into a string, and you'll see why in a second. You reduce over this data. Let's add these up. You're going to go and the function that you're providing is going to be called, for each of these values, one at a time in order from left to right.

[06:22] The index, the first time it's called is going to be zero and then one and then two and then three and then four. I just want to log that out so you can see. This is just going to be a regular sum operation. We're going to return accumulator plus value.

[06:40] What is this going to resolve to? Anybody want to take a guess? If you guessed 15, you're wrong. Resolved to 105, because in JavaScript a number plus a string is actually a string concatenation operation.

[06:58] Our accumulator starts at zero. We add 1, then we add 2 to that, then we add 3 to that, then we add 4 to that and we're at 10. We add the string 5 to 10 and that gives us 105. As you can see, our index started at zero, then one, then two, then three, then four.

[07:16] But maybe it's really important to you. Maybe this isn't a bug. Maybe you really, you have this string five and you really need to start with this. You want to reduce this array so that it starts here and then works its way down. When you do a four loop you can count down instead of counting up. You want to be able to do that for reduce.

[07:38] Is there some way to do that? Sure. Instead of calling array.reduce, call array.reduce right. Now you can see we're counting down from index four to three to two to one to zero. Our final output is what happens when you append the string zero to the string five to the string four to the string three to the string two to one.

Claudio Guerra
Claudio Guerra
~ 6 years ago

With this you could flatten virtually infinitely nested arrays :)

function flatten(data){return data.reduce(function(acc, value){return acc.concat(value.constructor === Array ? flatten(value) : value)}, [])}

Pavlo Iuriichuk
Pavlo Iuriichuk
~ 6 years ago

how about using a filter instead of forEach inside the reducer?

Karl Hadwen
Karl Hadwen
~ 5 years ago

Wouldn't it make more sense to use:

const cast = movies.reduce((acc, cur) => [...new Set(cur.cast)]);

Markdown supported.
Become a member to join the discussionEnroll Today