How to use reduce
in javascript
Reduce is one of those functions that seems to get a marmite reaction. Some people love it, and others hate it.
I'm primarily a .NET developer and am a big fan of LINQ when it comes to collections. The two most commonly used methods from this library are likely to be Select
and Where
, which in JavaScript correspond to map
and filter
, and are used in pretty much the same way.
const values = [1, 2, 3, 4]
var doubled = values.Select(x => 2*x); // returns [2, 4, 6, 8]
var odds = values.Where(X => x % 2 != 0); // returns [1, 3]
const values = [1, 2, 3, 4]
const doubled = values.map(x => 2*x) // returns [2, 4, 6, 8]
const odds = values.filter(x => x % 2 !== 0) // returns [1, 3]
But when I first came across reduce I realised I didn't know what the LINQ equivalent was. It's Aggregate
btw, but the reason I didn't know is because I had simply never needed it. This isn't because this type of function is useless, but because LINQ provides a host of other more specific aggregate functions, especially if you also use MoreLINQ as we do.
Useful Aggregates
The sort of aggregate functions I immediately started to use reduce for were things like Sum
, Min
, Max
, Distinct
, etc.
The same result can usually be achieved by using a forEach
loop and there's no reason why you can't. My preference for using reduce is that the code often looks very similar, but is still a pure function that doesn't rely on mutable variables.
Sum
Consider these approaches to adding an array of numbers using forEach
and reduce
(there will be a full explanation of the code in the next section).
forEach
let total = 0;
values.forEach(x => {
total += x
})
reduce
const total = values.reduce((prev, curr) => {
return prev + curr
}, 0)
The forEach
depends on a variable value which can be changed, and wraps this in a closure allowing it to be progressively added to, where the reduce
implementation is a pure function the result of which goes directly in to an immutable constant.
Reduce
The reduce
function takes in two arguments
- The reducer
- An optional initial value
The reducer is the part that confuses most people. The reducer is a function that will perform the aggregation one value at a time. If you've seen the MDN documentation then you know that the reducer can accept up to 4 parameters, but typically you only need the first two. I always call these two parameters prev
, and curr
. It's worth noting though that prev
is not the previous value in the array but the previous value returned by the reducer. Continuing with summing as an example:
Sum
const values = [1, 2, 3, 4]
const reducer = (prev, curr) => {
return prev + curr
}
const total = values.reduce(reducer, 0)
I've extracted the reducer into a separate variable just to make it clearer which part of the above I'm talking about. This reducer function will be called once for each value in the array.
The first time we come in prev
takes the value of the second parameter passed to reduce
, in this case 0
(If we didn't specify an initial value it would be undefined
). curr
would be the first value from the array. It adds the two and returns the result. The next time the reducer is called this result will become the prev
value. See the table below for what happens to each parameter is it loops through the array.
Loop # |
prev value |
curr value |
Returned value |
---|---|---|---|
1 | 0 | 1 | 1 |
2 | 1 | 2 | 3 |
3 | 3 | 3 | 6 |
4 | 6 | 4 | 10 |
The final result 10
would be returned from the reduce
function and stored in the total
constant.
Max
Another example, this time we'll find the greatest number in an array of numbers.
const values = [15, 6, 12, 24, 3, 11]
const max = values.reduce((prev, curr) => {
return prev > curr ? prev : curr
})
This time our table of values will look like:
Loop # |
prev value |
curr value |
Returned value |
---|---|---|---|
1 | undefined |
15 | 15 |
2 | 15 | 6 | 15 |
3 | 15 | 12 | 15 |
4 | 15 | 24 | 24 |
5 | 24 | 3 | 24 |
6 | 24 | 11 | 24 |
With 24
as our final result.
Aggregates with different types to the array
So far the return type of our reducer has been the same as the input types, which means that both prev
and curr
parameters have also been of the same type, but this is not always the case.
In this example we'll convert an array of objects into a javascript object. This can be useful for using it as a dictionary.
const values = [
{id: 106, name: "Wibble"},
{id: 357, name: "Wobble"},
{id: 652, name: "Flibble"}
]
const valuesDictionary = values.reduce((prev, curr) => {
return {
...prev,
[curr.id]: curr
}
}, {})
console.log(valuesDictionary[652]) // outputs "{id: 652, name: "Flibble"}"
This example makes use of the spread operator to take the properties of the prev
parameter and add them all to the new object the reducer returns. The end result is a JS object which you can use as a dictionary to look up each item by its id.
The above achieves the same result as .NET's ToDictionary
method.
Top comments (0)