Day 11: Higher Order Functions Part 2
Lesson 4: reduce
Ok, here we go - I've saved the hardest, but most powerful, until last.
reduce
allows you to change an array to pretty much anything you want, a single number, a long string, a boolean or an object.
Sounds wild right? Well it is, but once you get your head around it, it really is incredibly powerful.
Let's start with the simplest example, reducing an array of numbers to a single value.
First of all the process in plain English:
In plain English we could take a list of numbers 1, 5, 10, 17
and sum them up with reduce
starting with 0 like this. The key concept here is that the end of each calculation is passed to the next calculation, we start with 0.
- 0 + 1 = 1 ➡️ return 1
- 1 + 5 = 6 ➡️ return 6
- 6 + 10 = 6 ➡️ return 16
- 16 + 17 = 33 ➡️ return 33
And so we get 33 as the result.
Let's look at the code
Ok, don't panic - let's break it down.
This is the function that we give to reduce:
1(runningTotal, number) => { 2 return runningTotal + number; 3};
That's pretty simple, the function gets given the runningTotal
and the number
in the current iteration across the array.
But where does running total come from?
Well if I change the code to this:
1(runningTotal, number) => { 2 const newRunningTotal = runningTotal + number; 3 return newRunningTotal; 4};
It should become clearer, it's the returned value from the last iteration of this function.
If you add this line:
1console.log(runningTotal);
Into the playground above on the line above return runningTotal + number;
then you'll see it get larger each time it runs.
Ok, but where does runningTotal
first come from you may be asking?
That's what the stray 0
at the end there is doing. It's the initial value that gets passed in on the first iteration. It's optional, but I would say you should always provide an initial value so you don't have any unexpected behavior.
So the full declaration for the function you pass into .reduce()
is:
1array.reduce((previous, current) => { 2 // code that processes previous and current 3 return newValue; 4}, initialValue);
newValue
above is what will be passed in as previous in the next iteration.
Remember, this function will be executed for each item in the array, one after the other and for each iteration the previous
value and the current
array element will be passed in.
Transforming an array
Arguably the most powerful part of reduce
is how we can use it to transform array into objects.
Let's take a look at a more complicated example to show this off.
Let's start with some data
1const students = [ 2 { name: "Harry Potter", house: "Gryffindor" }, 3 { name: "Hermione Granger", house: "Gryffindor" }, 4 { name: "Ron Weasley", house: "Gryffindor" }, 5 { name: "Draco Malfoy", house: "Slytherin" }, 6 { name: "Luna Lovegood", house: "Ravenclaw" }, 7 { name: "Cho Chang", house: "Ravenclaw" }, 8 { name: "Cedric Diggory", house: "Hufflepuff" }, 9 { name: "Severus Snape", house: "Slytherin" }, 10 { name: "Ginny Weasley", house: "Gryffindor" }, 11 { name: "Tom Riddle", house: "Slytherin" }, 12 { name: "Sybil Trelawney", house: "Hufflepuff" }, 13 { name: "Gilderoy Lockhart", house: "Ravenclaw" }, 14];
Ok, that's a long list of students and their houses, let's say we want to change the structure of this data and have it look like this:
1const school = { 2 Gryffindor: [], 3 Slytherin: [], 4 Ravenclaw: [], 5 Hufflepuff: [], 6 totalStudents: 0, 7};
We could do this with a forEach
like this:
Which is a totally fine and dandy way of transforming the data .... however it's a little messy. We've had to set some data up before and after the main bulk of the work, this requires us to know which houses we have ahead of time (not a problem here but there are plenty of data processing cases like this where that's not the case).
Let's do the same but with reduce instead.
I'll explain the code:
1(school, student) => { 2 // explained next 3}, {totalStudents: 0})
So this is the function we pass into reduce()
. Note the initial value, I have set it to {totalStudents: 0}
with no houses. I could have added the houses here and used the same initial object as in the forEach()
example but I want to show you a trick for creating an object when you don't know the keys ahead of time. I've set totalStudents
because I wanted to highlight that you don't have to start with an empty object.
We could have seeded it with {}
or:
1const school = { 2 Gryffindor: [], 3 Slytherin: [], 4 Ravenclaw: [], 5 Hufflepuff: [], 6 totalStudents: 0, 7};
Each is fine, it would of course change the code in the body of the function we pass to reduce()
but that's fine. There's not really a right or wrong here (which is the beauty of reduce()
as far as I'm concerned ☺️)
Ok, about that trick I wanted to show you when you don't know all the keys your object will have:
1if (!school[student.house]) { 2 school[student.house] = []; 3}
Because our initial value has no houses (just totalStudents
) we need to check if the house for this student exists in the school
object. If it doesn't then add it and create a new array, if it already exists (which it will if we have already added a previous student into this house) then it will skip this step.
1school[student.house].push(student.name);
This line is the same as the forEach
example, we add a student into it's house array.
1school.totalStudents += 1;
We know totalStudents
is on the object as that's the on property we seeded the object with, so we increment it by one.
1return school;
We return the school
object for use in the next iteration.
Why use reduce()
?
So you may think:
Ok, that's great but why use
reduce()
here and not stick with theforEach()
example?
Which is totally fair, the reason being is that you can chain all of these higher order functions together. Here's an example:
Look at that, we've filtered out all the Slytherin students ... if only it had been that simple!
Challenges
Let's flex your newly found reduction skills. Use reduce to create an object with this structure:
1const result = { 2 students: [], // student array like it is now 3 houses: [], // an array of the 4 house names 4 averageAge: 0, // average age of the students 5};
This is a is a decent sized challenge and will stretch your brain - that's a good thing. It will really help you learn it 😁
Note that I've added each students rough age