Here’s the thing: I really don’t think map, reduce and filter are for beginner programmers.
Not because they’re “hard” or “too much” for beginners.
Just because they make an “obvious”1 operation less obvious. Once we’re familiar with these functions it’s easy to take what they do for granted.
Often it’s because someone asks why their for loop isn’t working, and they’re answered with “Just use Array.map”, along with a working example.
The person asking the question can see that the Array.map solution works, but they can’t see why.
And they still haven’t learnt anything about their for loop.
So if that’s you, and you’d like to know more about how map, reduce, etc work, this article is for you.
Before we do anything else, I’ll start with the use-case that gave birth to reduce (spoiler: you want to reduce a list of thing to a single value).
Let’s imagine we have an array, and we want to add all the elements in the array together:
That’s ok, but we might like to do that more than once, right? So let’s make a function out of it:
Now we can use our function to repeatedly add numbers in arrays together2. Now we can add like maniacs all through our code.
But now we need to multiply all the values in an array as well. Okaaay.
The natural next action is to reproduce the sumArray with a different name, and swap the += with *=.
Easy right? It could look like this:
But whenever we find ourselves copying and pasting code (or even just laboriously re-typing it) that’s a little signal we’re potentially creating future pain for ourselves.
Anytime we’re coding there’s an opportunity to introduce an error. Less coding = less errors.
Even if we get it exactly right on the first try (I didn’t just now when I copied sumArray to make multiplyArray), that’s still more code to maintain in the future. Pain.
So the first step is to ask how we can generalize our sumArray and multiplyArray functions into one function that goes through every element in an array and does something with it.
So we’ll reboot the code a bit. I’ll make a looping function (pressArray, you know, like cold-pressed oil), and then some functions that add and multiply:
Uh oh – check the output! The product has gone from 768 to 0!
So we’ve got a general function that loops through an array, and calls whatever function we give it.
That works fine for adding. But multiplication isn’t working because the number we start with is 0. We end up calculating 0 * 8 * 4 * 4 * 3 * 2. Which is 0.
So we need to be able to say what our beginning value is. In this case we want 1, like in the original version of multiplyArray.
Since our code can’t guess what we’re doing to the array, we’ll have to pass the initial value in:
What we’ve created is a very simple version of Array.reduce.
Besides input validation, the big difference is that we haven’t added our pressArray function to Array’s prototype. This is currently furiously frowned upon, but if you want to see what it would look like, here it is, renamed press:
So we’ve reproduced Array.reduce, which takes an array and calculates a single value from that array. Here’s reduce doing the same thing:
But what if we want to change every element in an array? We need something that will return a new array with updated elements.
That’s what Array.map is for.
Array.map works very similarly to Array.reduce: It loops through every element of an array, and returns a new array. The new array is based on the old array, but with the elements modified.
Let’s take an example. Say we’re given an array of student test scores, and we need to turn the scores into percentages:
Now that we know how this works, we can do the same thing we did earlier, and turn our for loop into a generalized function for converting elements in an array. I’ll call the new function ‘convert’:
Of course, our convert function works just like Array.map, which we can see in action here:
(Yep, I skipped the bit where we added convert() to Array.prototype.)
So far we’ve covered:
But what if we only want a subset of an array? That’s where filter comes in.
Let’s stick with the student scores for now. We’ve kept our copy of student score percentages, and want to get all the scores that are more than 50%.
This is the kind of thing Array.filter is used for. Once again, we can make our own version with a simple for loop:
So you can see this is similar to map: we return an array based on the input array. But this time we don’t transform the elements, we just pick and choose based on a criteria. In this case, is the score greater than or equal to 50%?
It can be generalized an a similar way to our map implementation:
And we get exactly the same result by using the Array.filter function instead:
Of course, while these functions save us from repeatedly typing out for loops, they have more to offer than that.
They really come into their own when they’re combined.
Notice in our most recent piece of code, that we save the output of map (the percentage values), which is an array, and then call filter on it.
It isn’t necessary to do this if we don’t need to save the percentage array. Instead, we can just chain the calls to the array functions like this:
Which is a way of performing a non-trivial query against our test score data with surprizingly little code.
I think it’s important for anyone just starting out programming to understand those details.
1. I’m not a fan of the word “obvious”! (e.g. “Well, obviously…“) But I’m always happy to talk about making anything ‘more obvious’ 🙂
2. We’re assuming a lot about the parameters here, and would normally see a lot more typechecks. See here for a well checked implementation.