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?
🍻
Top comments (12)
I can't take credit for the image, but it seemed to helped me to remember how to use each 😉
This tweet appears to be the original
This is awesome, do you have the source for this image?
I'm not sure of the original source, but I know I've seen it circulating on twitter a few times.
Woah, this is too good!
Haha this is great.
Each of
map
,filter
, andreduce
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). Somap
'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 returnstrue
. Likemap
, 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 anif
in thereduce
callback!). You can usereduce
to transform an array into something else entirely, like you're trying to do here.There is a simple two-step solution involving
map
andjoin
, butreduce
will do it in one. I'll leave implementing the callback to you, but you're looking atconst ingredientString = jsIngredients.reduce((accumulator, ingredient) => {...}, '');
.Also, doublecheck your original array -- based on your loop, the
ingredient-x
key should just be a consistentvalue
instead.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:
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:
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:
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 justingredient
instead ofingredient-1/2/3
, then you can access them withjsIngredients[i].ingredient
. I'll assume you made this change and carry on)So, breaking down what reduce will do:
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 accumulatorreduce
will call your reducer function, passing it the initial accumulator value and the first element of your arrayreduce
then calls your reducer function again, passing the new accumulator value and the next element of your array.And in code:
Note the passing of an empty string to
reduce
as the initial value. Also note this is a very rough idea of howreduce
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. 😁
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
This is a
reduce
machine.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
andfilter
. Maybe thinking about how you'd make your own version of these functions could be helpful:Let's create our own filter method in JS
machy44
Let's create our map method
machy44
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:
A normal synchronous version should be a pretty simple cleanup of the above code:
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.
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!