- Overview
- Transcript
3.2 Understanding Map and Reduce
Now that we're getting the hang of making small, reusable functions, we can begin to tackle new problems beyond just filtering our list of beers. In this lesson we'll compute the average alcohol by volume (ABV) for each set of displayed beers, and we'll do it in a functional style using map and reduce.
1.Introduction3 lessons, 14:53
1.1Introduction01:13
1.2A Refresher on Functions08:57
1.3Project Walkthrough04:43
2.Some Basic Improvements5 lessons, 36:51
2.1Getting Declarative09:20
2.2Making our Filter Declarative08:21
2.3Becoming Familiar With Higher-Order Functions05:22
2.4Callbacks, the Other Higher-Order Function06:15
2.5A Look at Pure Functions07:33
3.Building a Functional Utility Library4 lessons, 29:00
3.1Functional Filter02:52
3.2Understanding Map and Reduce10:41
3.3Reorganizing Our Collection With GroupBy08:13
3.4Getting Creative With Pluck and Mean07:14
4.Some Existing Tools2 lessons, 14:47
4.1Underscore and Lodash07:55
4.2Native JavaScript Functions in ES506:52
5.Conclusion1 lesson, 02:36
5.1Conclusion02:36
3.2 Understanding Map and Reduce
All right. So, with our new functional filter utility that we just made in the last video, we've got the groundwork laid for creating more of these functional utilities. And so, in this video, we're gonna pretend that the client whose asked us for this Beer List website, has put in a new feature request. We wanna actually add the average ABV from all of our currently available beers to the bottom of the list. And we want it to update whenever we click on one of the filters. So if you've been following along, you should have predicted that the answer to this question is going to start with a function. So let's define our new function up here. Function getAverageAbv. And because we wanna make our functions pure, we don't wanna rely on any kind of variable that's outside of the scope, so we're gonna pass it in. So what do we need to do to get the average ABV? We need to loop over the beer list, we need to pull out each ABV value. We need to add them altogether and then divide them by the length. And we could do a for loop and loop over the beers like we've been doing, and then convert it to a more functional style. But I think you all have been watching and get it now, and so let's just jump right into the functional style. We're gonna use a functional utility called map. What map does is it does the looping and it applies a callback to each item in the collection. It's going to take a collection and a callback. It's very similar to how filter works. [SOUND] We'll start out with a variable called mapped, just like filter, we'll do the loop, actually I'm just gonna copy this. [SOUND] Except the difference here is we're not doing this if statement. Okay, so we'll get rid of that. If we just left it like this, we'd obviously be returning back the collection exactly as it was passed to us. So this is basically, I have noop function, which does nothing. What we wanna do instead is insert the callback, right here. [SOUND] And that's as simple as that. What we're doing is saying for each item, we'll run the callback, and whatever returns based on that item, is what we'll replace that item in the new array. We can basically get an array of ABVs this way. So if we said var abvs equals and then we'll call it map, we'll pass it to beers, and then our function. We'll get a, take a beer each time, and we'll return beer.abv. Now, let's look at the data just to make sure you understand where this is coming from. So here's our data, and let me just get rid of our sidebar here. Okay, so now I can see them better. So we have the name, the type, the locale and then the ABV. So each object in this beers array has that value. And so by returning the AVB, the resulting array will now just to be the AVBs themselves. So that's what map does. And now we need a way to loop over the resulting array of numbers. Now we have just a list of numbers. We're going to loop over them and add each one to the previous one. So this is a way to basically combine a bunch of items in an array, down into one array. Or what you might say is to reduce an array down to one item and so the way we do that is with a utility called reduce. Takes a collection and a callback [SOUND] instead of a mapped or a filtered array, we don't want our resulting array, you know? We're not transforming one array to another like filter and map does. Here, we're reducing an array down to a value. So, we'll just save off last, and then, we'll do our for loop. We'll set last equal to the callback. We'll pass in the current value of last, and then, we'll also pass in as the second argument, our collection item. So, the item that we're currently on as we're looping over. The one case we didn't, we have to account for here is the fact that on the first time through, so the first time through last is undefined. Here, and we'll now say var total equals reduce abvs, [SOUND] and this is going to take a and b, [SOUND] now, right and left kind of a thing, and return a plus b. In this case, the first time through, we're gonna have undefined plus one, and that's gonna cause a problem, because addition with undefined is just gonna cause unexpected results. So, the way this is commonly handled, and it's the way we're gonna handle it, is by allowing for an initial value to come in. So if you pass in a third argument, remember, a, arguments are optional, so you don't have to. If you're gonna deal with this case in your callback, which we could do, we could say, you know, if not a then set a to zero, or something. But that's messier in my opinion, and I think we should just be able to set in, send in an initial value. And so we'll just set that right here. Remember, if it's undefined when it comes in, then last will be set to undefined which is what it was before we did this. So, we're actually not losing anything if they don't send in an initial value, but if you do, then the first time through this loop will have that initial value set. And that way, we can say 0 is our initial value. And just to kinda see what's going on here before we move on, let's get this writing out to the template. So back in our index file, we already have our beer list here. I'm just gonna add another tag, paragraph tag with an id of averageAbvs. We copy that. The top of our function, or, sorry, the top of our JavaScript file where we're gathering all of our elements. The element by that ID. So, there is that element, and then in loadBeers, whenever we load the beers, we wanna also load the averageAbv. So, oops, I spelt this wrong. Let me grab that, averageAbv. So averageAbv.innerHTML [SOUND] should now equal getAverageAbv of the beers. And because we're passing the past in beers array, when we're loading up a set of filtered beers, it will automatically do the calculation against the filtered beers. So we'll have that dynamic ABV already set for us. Let's go see what happens now. Refresh, at the bottom of the page, undefined. Let's see why that's doing that. Oh, you know what it is? Our function is not pure because it doesn't return anything, right? So I'm gonna return the total for right now and that will at least give us something to look at. [SOUND] Here we go. So it's now turning 79.1, which we kinda have to trust. If we go to Stouts, should see that yeah, 13.6, that looks right. So it's adding together all these values. So we still have to do a couple more steps. So what we wanna do here is divide the total by beers.length. Now, that's gonna give us kind of a nasty looking average. Let's take a look at this. Yeah, so that's the average ABV. Not crazy about that, so let's see if we can round this down to one decimal place just like we do here. And the way we'll do this is we're gonna have to kinda use some JavaScript tricks. Let's say Math.round, and that will take it down to just six, or seven I should say, right? It rounds it to the nearest integer. But if we want that one decimal place back, then we have to do a little bit of nonsense here. So, round that value times 10. [SOUND] And then at the end of that, we'll divide it by 10 again. And so that will give us that one decimal place. [SOUND] 6.6, 6.6. Is it changing? [SOUND] It is, 7, 5.6. So just by random chance, we have domestic and imports are both 6.6 on average, which means the whole thing is also 6.6. [SOUND] And of course, we could play with these, we can make a few of our domestics go up, so make this one be 8.9, we're gonna have a really rowdy bar, this one is domestic, so let's make that 8.5. This one is domestic, let's make this 6.8, all right. So we just pushed up a couple of our domestics and if we refresh now, now our average is 7, our domestic average is 7.2, and our import average is still 6.6. So we're verifying that it is working. It's dynamically calculating those. And the last change we'll make, just to be a little more clear with what's happening, so we'll actually say averageAbv colon plus, I'll keep our space in there. [SOUND] And then plus a percent sign. So this will make it look a little nicer. So now the average ABV is 7%. The last thing I'm gonna do just because it's fun for me here is to demonstrate the declarativeness here. You see this function in reduce? That's a really common function. So, why don't we actually pull this out in case we wanna use it somewhere else? So, we'll call it add, it'll take an a and a b, and it just returns to a plus b. It's just simple function that it seems like we shouldn't define that in multiple places, in case we need it somewhere else. And this is a little premature, like if we were only using this in this one place, we may not wanna do this. But just to demonstrate how you can, we can now just pass a reference to add, and now this looks even nicer, right? We're saying reduce the ABVs, add them together starting with a 0. Okay, if you know how reduce works, you can read this in a way that makes some good sense. So that's how we use map and reduce. In our next video, we're gonna talk about another fictional client request where we'll need to use something called grouped by.