RC Maples

Posted on

# Explain it to me like I'm five: .map, .reduce, & .filter edition

I'm having trouble understanding how to use map, reduce, and filter to iterate over an array (or an array of objects for that matter π).

I generally use for loops (and nested for loops if needed), but would really like to switch over to map, reduce, and filter for various things. I just can't wrap my head around how it works and what it's doing.

Here's a sample bit of code where I think I could use map/reduce/filter to achieve the same results a bit cleaner.

``````const jsIngredients = [
{"ingredient-1":"chicken"},
{"ingredient-2":"brocolli"},
{"ingredient-3":"cheese"}
];

let ingredientString = "";

for (let k = 0; k<jsIngredients.length; k++) {
if (jsIngredients[k].value) { // if non-empty
ingredientString +=  `\${jsIngredients[k].value},`;
// ingredientString = "chicken,brocolli,cheese,"
}
}
ingredientString = ingredientString.slice(0,ingredientString.length-1);
// ingredientString = "chicken,brocolli,cheese"
``````

Any help?
π»

Brett Stevenson

I can't take credit for the image, but it seemed to helped me to remember how to use each π

Evan Oman

This tweet appears to be the original

Quentin Sonrel

This is awesome, do you have the source for this image?

Brett Stevenson

I'm not sure of the original source, but I know I've seen it circulating on twitter a few times.

Humza K.

Woah, this is too good!

Jochem Stoel

Haha this is great.

Dian Fay • Edited

Each of `map`, `filter`, and `reduce` steps through an array element by element and does something with each element, where 'something' is defined by the callback function you pass.

`map` applies a transformation to the element, so its callback function just takes the element itself (there are extra arguments, like the current index, in case you need them). So `map`'s output is an array just as long as the original array, but where each element has been transformed from the original.

`filter` accumulates only those elements for which the callback function returns `true`. Like `map`, the callback operates on the original element with the same extra arguments, but it has to return a boolean-ish value. `filter`'s output is an array which is either shorter or the same length as the original, and which contains only those elements for which the callback function returns a truthy value.

`reduce` works on a separate value which accumulates changes from each array element. Its callback is a little different: the first argument is the accumulator, then the element, then the extra arguments. It has to return the accumulator once it's been updated (or even if it hasn't -- `filter` + `reduce` is a waste of time, just use an `if` in the `reduce` callback!). You can use `reduce` to transform an array into something else entirely, like you're trying to do here.

There is a simple two-step solution involving `map` and `join`, but `reduce` will do it in one. I'll leave implementing the callback to you, but you're looking at `const ingredientString = jsIngredients.reduce((accumulator, ingredient) => {...}, '');`.

Also, doublecheck your original array -- based on your loop, the `ingredient-x` key should just be a consistent `value` instead.

Alex Lohr • Edited

Imagine you have small machines attached to arrays that take a function and use the array to do stuff with it.

• map: this will take each item of the array it is attached to, runs it through the function and will return a new array with the results:

``````[1, 2, 3].map(x => x + 1) // [2, 3, 4]
``````
• reduce: very much the same as map, but instead of an array, it will provide the function with the result of the last operation (or at the start the second argument it received) and returns the single last result instead of an array:

``````[1, 2, 3].reduce((r, x) => r + x, 0) // 6
``````
• filter: this will return an array of all the values of the array it was attached to that had the function return a true-ish result:

``````[1, 2, 3].filter(x => x % 2 === 1) // [1, 3]
``````

Kevin Smith π΄σ §σ ’σ ³σ £σ ΄σ Ώ

If you have an array of things and what you want is a single thing, `reduce` is what you need.

It works by using a variable known as the 'accumulator', which is just the result you build up after processing each element in your array. In your case, that'd be `ingredientString`.

(I should point out at this point that your code as posted doesn't work - `jsIngredients[i].value` will always be undefined. Give the objects in your array consistent keys, like just `ingredient` instead of `ingredient-1/2/3`, then you can access them with `jsIngredients[i].ingredient`. I'll assume you made this change and carry on)

So, breaking down what reduce will do:

• You pass `reduce` two things: a function (the 'reducer' function) which takes the 'accumulator' value and an element of your array, and an initial value for the accumulator
• `reduce` will call your reducer function, passing it the initial accumulator value and the first element of your array
• Your reducer function does whatever you like with the accumulator and your array element. It should eventually return a value, and that value will become the new value of the accumulator.
• `reduce` then calls your reducer function again, passing the new accumulator value and the next element of your array.
• Repeat until no more elements remain.

And in code:

``````const jsIngredients = [
{"ingredient":"chicken"},
{"ingredient":"brocolli"},
{"ingredient":"cheese"}
]

let ingredientString = jsIngredients.reduce((acc, element) => {
if (element.ingredient) {
return acc + `\${element.ingredient},`
}
return acc
}, '')

ingredientString = ingredientString.slice(0,ingredientString.length-1)
// "chicken,brocolli,cheese"
``````

Note the passing of an empty string to `reduce` as the initial value. Also note this is a very rough idea of how `reduce` is typically used, see developer.mozilla.org/en-US/docs/W... for more details.

I also wrote goo.gl/9sQAQw but only ever got around to explaining map, still might be useful.

Finally, rather than writing the ingredient string manually and having to slice out the extra comma, consider how you might adapt this to use Array.prototype.join to solve this for you. π

Alain Van Hout • Edited

Think of it like a car factory, where stuff goes in and cars come out, with long assembly lines all over the place. Along these assembly lines, there are places where stuff goes into a machine, and other stuff comes out at the other end.

Closely observe one of those machines: on the left side, pieces of raw metal go in, and on the right side, for each of those pieces of raw metal, a metal screw comes out. Some of the metal pieces are copper, other are steel, but all come out as the same kind of screw (though in different kinds of metal). In essence, the machine takes in raw metal pieces and transformed them into screw. That machine is `map`.

There are other machines that take in the screw, and then look at the quality of the screw. It lets pass the ones that are okay, but removes the bad ones (without transforming the screws in any way). You are currently looking a `filter` machines.

Finally, there are also other machines that take in lots of pieces, and output a single combined piece. It doesn't just lump them together all at once, but rather

1. starts with a single piece that it's given
2. takes a second piece and combines it with the first to form a new single piece
3. takes a third piece and combines that with the newly created piece, to form another newly created piece
4. repeats this until there are no more pieces to be had
5. puts the lastly created single piece on the assembly line to continue on

This is a `reduce` machine.

Nested Software • Edited

There are already some good comments others have made, but I'll add a small comment of my own. Here are two articles written by @machy44 where he implemented his own versions of `map` and `filter`. Maybe thinking about how you'd make your own version of these functions could be helpful:

A simple version of reduce would be implemented in a similar vein.

Here is a version of reduce I wrote for my article on asynchronous generators:

``````const asyncReduce = async function* (iterable, reducer, accumulator) {
for await (const item of iterable) {
const reductionResult = reducer(item, accumulator)

accumulator = reductionResult

yield reductionResult
}
}
``````

A normal synchronous version should be a pretty simple cleanup of the above code:

``````const syncReduce = function (iterable, reducer, accumulator) {
for (const item of iterable) {
const reductionResult = reducer(item, accumulator)

accumulator = reductionResult
}

return accumulator
}
``````

As @andeemarks points out, these functions basically abstract away the `for` loop boilerplate.

I thought I'd also add that these are not the only ways to do this kind of thing. For example, Python has list comprehensions that I think are often more clear. I don't think JavaScript has them though.

RC Maples

Awesome! Thanks for the feedback, sorry my code example is a bit crap. I pulled some live code and then realized some pieces were missing and just kinda slapped something in there. Really appreciate the different approaches to the topic!