A Case For Reduce Part 1
6/6/2021
JavaScript provides many useful functions to loop through an array and execute an operation on each element of the array like forEach
, filter
, map
, reduce
, find
, some
, every
and many more.
Filter
and map
are generally over while forEach
and reduce
are not only less used but reduce
also receives a lot of hate for "making" the code harder to read and not being useful at all.
During my career as a developer I have learned about clever and useful ways of effectively applying reduce
and making it an important tool to have in your belt as a programmer. Regardless of the programming language that you use, it is important to learn the tools that your language offers and make them work to solve the problems that you are facing as a programmer.
So my plan with this post is to make a bit of a case for useful ways of applying reduce
for solving problems, rather than explaining how reduce works in a totally beginner friendly manner. So if you have any questions feel free to write me on Twitter.
What is reduce anyway?
reduce
as all of the other methods mentioned above receives one function which is executed on each element of the array.
In the case of reduce
it also receives an initial value which is optional.
reduce(reducerFn)
reduce(reducerFn, initialValue)
The function executed for each element is called a reducer
and its signature looks like this:
function reducer(accumulator, currentValue) {
...
}
The reducer
receives 2 more parameters which are optional but in this post we're going to be focusing in the accumulator
and currentValue
.
So currently we have some terminology that we should care about like:
- initial value: The value used to initialize the
accumulator
. It could be a Number, a String, an Array or any other data type. - reducer: the function that we execute on each element of the array.
- accumulator: the value we end up with, after executing the reducer.
- current value: the value we are processing during the iteration of the array.
This terminology already gives us some insight of how reduce
works.
A replacement for map and filter
We have an accumulator
as the value we will receive at the end of the reduce
execution and we have access to it in the reducer
the function that is execute on each element of the array. This means we have full control of what actually goes in the accumulator
and it could be a totally different data structure from the one we started with in the array, giving us the ability to construct the signature of the data as we want it just like map
does but unlike map we can decide to populate this accumulator
with data based on conditionals just like filter
does.
So essentially if you have to do filtering and mapping of your data, using reduce is a better option that the commonly used:
arr.filter(filterFn).map(mapFn)
You can use reduce
and iterate the array only once.
For example if we have this comma separated file:
1,Wynton Kelly,piano
2,John Coltrane,saxophone
3,Miles Davis,trumpet
4,Herbie Hancock,piano
5,Bill Evans,piano
6,Oscar Peterson,piano
we want to read it line by line and store it in an array of objects but only when the instrument is piano we could use map
and filter
for this:
const procesed_map_filter = musicians.map((line) => {
const musician = line.split(',')
return({
id: musician[0],
name: musician[1],
instrument: musician[2]
})
}).filter((musician) => musician.instrument === 'piano')
We use map to transform the array of strings into an array of objects with the interface we specified for our data and we use filter to select only the musicians that play the piano. This two functions execute independently of each other and they both iterate over an array one after the other.
We could solve the same problem with a reducer iterating only once over the array and doing the map
and filter
inside of the reducer
function :
const reducerFn = (accumulator, currentValue) => {
const musician = currentValue.split(',')
if(musician[2] === 'piano') { // we filter
accumulator.push({ // and we map
id: musician[0],
name: musician[1],
instrument: musician[2]
})
}
return accumulator
}
const initialValue = []
const procesed_reduce = musicians.reduce(reducerFn, initialValue)
It might look like a bit more code but I made it explicitly verbose with the variable declarations so we could see the important terminology. So we could reduce
haha its footprint more if we inline the reducer
function and the initialValue
like we did with the map
and filter
. And we are also iterating over the array only once.
Both produce the same data structure:
[
{ id: '1', name: 'Wynton Kelly', instrument: 'piano' },
{ id: '4', name: 'Herbie Hancock', instrument: 'piano' },
{ id: '5', name: 'Bill Evans', instrument: 'piano' },
{ id: '6', name: 'Oscar Peterson', instrument: 'piano' }
]
Other reduce uses
My plan is to keep this posts short so this is part 1 and other parts will come soon with examples of other scenarios where reduce
can be a better option and what other options we could have if we still don't want to use reduce
.