Understanding the Almighty Reducer
Publikováno: 13.6.2018
I was reently mentoring someone who had trouble with the .reduce()
method in JavaScript. Namely, how you get from this:
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
...to this:
const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)
They are functionally equivalent and they both sum up all the numbers in the array, but there is …
The post Understanding the Almighty Reducer appeared first on CSS-Tricks.
I was reently mentoring someone who had trouble with the .reduce()
method in JavaScript. Namely, how you get from this:
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
...to this:
const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)
They are functionally equivalent and they both sum up all the numbers in the array, but there is a bit of paradigm shift between them. Let's explore reducers for a moment because they're powerful, and important to have in your programming toolbox. There are literally a million other articles on reducers out there, and I'll link up some of my favorites at the end.
What is a reducer?
The first and most important thing to understand about a reducer is that it will always only return one value. The job of a reducer is to reduce. That one value can be a number, a string, an array or an object, but it will always only be one. Reducers are really great for a lot of things, but they're especially useful for applying a bit of logic to a group of values and ending up with another single result.
That's the other thing to mention: reducers will not, by their nature, mutate your initial value; rather they return something else. Let's walk over that first example so you can see what's happening here. The video below explains:
It might be helpful to watch the video to see how the progression occurs, but here's the code we're looking at:
const nums = [1, 2, 3]
let value = 0
for (let i = 0; i < nums.length; i++) {
value += nums[i]
}
We have our array (1, 2, 3
) and the first value each number in the array will be added to (0
). We walk through the amount of the array and add them to the initial value.
Let's try this a little differently:
const nums = [1, 2, 3]
const initialValue = 0
const reducer = function (acc, item) {
return acc + item
}
const total = nums.reduce(reducer, initialValue)
Now we have the same array, but this time we're not mutating that first value. Instead, we have an initialValue
that will only be used at the start. Next, we can make a function that takes an accumulator
and an item. The accumulator is the collected value
returned in the last invocation that informs the function what the next value will be added to. In this case of addition, you can think of it as a snowball rolling down a mountain that eats up each value in its path as it grows in size by every eaten value.
We’ll use .reduce()
to apply the function and start from that initial value. This can be shortened with an arrow function:
const nums = [1, 2, 3]
const initialValue = 0
const reducer = (acc, item) => {
return acc + item
}
const total = nums.reduce(reducer, initialValue)
And then shortened some more! Implicit returns for the win!
const nums = [1, 2, 3]
const initialValue = 0
const reducer = (acc, item) => acc + item
const total = nums.reduce(reducer, initialValue)
Now we can simply apply the function right where we called it, and we can also plop that initial value directly in there!
const nums = [1, 2, 3]
const total = nums.reduce((acc, item) => acc + item,
An accumulator can be an intimidating term, so you can think of it like the current state of the array as we're applying the logic on the callback's invocations.
The Call Stack
In case it's not clear what's happening, let's log out what's going on for each iteration. The reduce is using a callback function that will run for each item in the array. IThe following demo will help to make this more clear. I've also used a different array ([1, 3, 6]
) because having the numbers be the same as the index could be confusing.
See the Pen showing acc, item, return by Sarah Drasner (@sdras) on CodePen.
When we run this, we'll see this output in the console:
"Acc: 0, Item: 1, Return value: 1"
"Acc: 1, Item: 3, Return value: 4"
"Acc: 4, Item: 6, Return value: 10"
Here's a more visual breakdown:
- It shows that the accumulator is starting at our initial value,
0
- Then we have the first item, which is 1, so our return value is
1
(0 + 1 = 1
) 1
becomes the accumulator on the next invocation- Now we have
1
as the accumulator and 3 is the item aince it is next in the array. - The returned value becomes
4
(1 + 3 = 4
) - That, in turn, becomes the accumulator and the next item at invocation is
6
- That results in
10
(4 + 6 = 10
) and is our final value since6
is the last number in the array
Simple Examples
Now that we've got that under our belt, let's look at some common and useful things reducers can do.
How many of X do we have?
Let's say you have an array of numbers and you want to return an object that reports the number of times those numbers occur in the array. Note that this could just as easily apply to strings.
const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]
const result = nums.reduce((tally, amt) => {
tally[amt] ? tally[amt]++ : tally[amt] = 1
return tally
}, {})
console.log(result)
See the Pen simplified reduce by Sarah Drasner (@sdras) on CodePen.
Wait, what did we just do?
Initially, we have an array and the object we’re going to put its contents into. In our reducer, we ask: does this item exist? If so, let's increment it. If not, add it and set it to 1. At the end, please return the tally count of each item. Then, we run the reduce function, passing in both the reducer and the initial value.
Take an array and turn it into an object that shows some conditions
Let’s say we have an array and we want to create an object based on a set of conditions. Reduce can be great for this! Here, we want to create an object out of any instance of a number contained in the array and show both an odd and even version of this number. If the number is already even or odd, then that’s what we’ll have in the object.
const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]
// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
acc[item] = {
odd: item % 2 ? item : item - 1,
even: item % 2 ? item + 1 : item
}
return acc
}, {})
console.log(result)
See the Pen simplified reduce by Sarah Drasner (@sdras) on CodePen.
This will shoot out the following output in the console:
1:{odd: 1, even: 2}
3:{odd: 3, even: 4}
4:{odd: 3, even: 4}
5:{odd: 5, even: 6}
6:{odd: 5, even: 6}
82:{odd: 81, even: 82}
OK, so what's happening?
As we’re going through every item in the array, we create a property for even and odd, and based on an inline condition with a modulus operator, we’ll either store the number or increment it by 1. The modulus operator is really good for this because it can quickly check for even or odd — if it's divisible by two, it's even, if not, it's odd.
Other resources
At the top, I mentioned other posts out there that are handy resources to get more familiar with the role of reducers. Here are a few of my favorites:
- The MDN documentation is wonderful for this. Seriously, it's one of their best posts, IMO. They also describe in a bit more detail what happens if you don't provide an initial value, which we didn't cover in this post.
- Daniel Shiffman is always amazing at explaining things on Coding Train.
- A Drip of JavaScript does a good job, too.
The post Understanding the Almighty Reducer appeared first on CSS-Tricks.