reduce() Method
The reduce(callback, initialValue)
method is used to process each array element, one by one, storing the intermediate result as an accumulator. It is a little more difficult to learn, but the result is worth it.
array.reduce((previousValue, element, index, array) => {
// Callback body
}, initialValue);
- Does not change the original array.
- Iterates over the original array element by element.
- Returns anything.
- Does anything.
The easiest way to see it at work is to calculate the amount of array elements.
const total = [2, 7, 3, 14, 6].reduce((previousValue, number) => {
return previousValue + number;
}, 0);
console.log(total); // 32
The first callback parameter (previousValue
) is the accumulator, that is, the intermediate result. The value returned by the callback function in the current iteration will be the value of this parameter in the next iteration.
You can pass an optional initial accumulator value, the initialValue
parameter, as the second argument for reduce()
.
# First, the reduce() method creates an internal accumulator variable and
# assigns it the value of the initialValue parameter or the first element
# of the array to iterate over if initialValue is not set.
previousValue = 0
# Next, a callback is called for each array element. The current value
# of the previousValue parameter is what the callback function returned in the last iteration.
Iteration 1 -> previousValue = 0 -> number = 2 -> return 0 + 2 -> return 2
Iteration 2 -> previousValue = 2 -> number = 7 -> return 2 + 7 -> return 9
Iteration 3 -> previousValue = 9 -> number = 3 -> return 9 + 3 -> return 12
Iteration 4 -> previousValue = 12 -> number = 14 -> return 12 + 14 -> return 26
Iteration 5 -> previousValue = 26 -> number = 6 -> return 26 + 6 -> return 32
# After iterating over the entire array, reduce() returns the accumulator value. Result: 32
That is, the reduce()
method is used when you need to take "many" and reduce to "one". In everyday tasks, its application comes down to operations with numbers.
Array of objects
When working with an array of objects, reducing is done by a certain property value. For example, there is an array of students with test scores. You need to get an average score.
const students = [
{ name: "Mango", score: 83 },
{ name: "Poly", score: 59 },
{ name: "Ajax", score: 37 },
{ name: "Kiwi", score: 94 },
{ name: "Houston", score: 64 },
];
// The accumulator name can be arbitrary, it is just a function parameter
const totalScore = students.reduce((total, student) => {
return total + student.score;
}, 0);
const averageScore = totalScore / students.length;
Advanced reduce
Let's say you have the following task: from the array of Twitter posts of an individual user, you need to count total likes. You can iterate over with the for
or forEach
loop, each of these solutions will require additional code. Or you can use reduce
.
const tweets = [
{ id: "000", likes: 5, tags: ["js", "nodejs"] },
{ id: "001", likes: 2, tags: ["html", "css"] },
{ id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
{ id: "003", likes: 8, tags: ["css", "react"] },
{ id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
];
// Iterate over all collection elements and add values of the likes property
// to the accumulator with 0 as initial value.
const likes = tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);
console.log(likes); // 32
// Counting likes is probably not a single operation, so let's write a function
// to calculate the likes from a collection
const countLikes = tweets => {
return tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);
};
console.log(countLikes(tweets)); // 32
Have you noticed the tags
property for every post? Continue with reduce
and collect all the tags in the user’s posts into an array.
const tweets = [
{ id: "000", likes: 5, tags: ["js", "nodejs"] },
{ id: "001", likes: 2, tags: ["html", "css"] },
{ id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
{ id: "003", likes: 8, tags: ["css", "react"] },
{ id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
];
// Iterate over all collection elements and add values of the tags property
// to the accumulator with an empty array [] as initial value.
// In each iteration, push all the tweet.tags elements into the accumulator and return it.
const tags = tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
console.log(tags);
// Collecting tags is probably not a single operation, so let's write a function
// to collect tags from the collection
const getTags = tweets =>
tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
console.log(getTags(tweets));
After collecting all the tags from the posts, it would be nice to count the number of unique tags in the array. The reduce
method will come in handy again.
const tweets = [
{ id: "000", likes: 5, tags: ["js", "nodejs"] },
{ id: "001", likes: 2, tags: ["html", "css"] },
{ id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
{ id: "003", likes: 8, tags: ["css", "react"] },
{ id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
];
const getTags = tweets =>
tweets.reduce((allTags, tweet) => {
allTags.push(...tweet.tags);
return allTags;
}, []);
const tags = getTags(tweets);
// Write the callback separately and pass a reference to it in reduce.
// This is standard practice when callback functions are big enough.
// If the acc accumulator object does not have own property with the tag key,
// create it and assign it 0.
// Otherwise, increase the value by 1.
const getTagStats = (acc, tag) => {
if (!acc.hasOwnProperty(tag)) {
acc[tag] = 0;
}
acc[tag] += 1;
return acc;
};
// The initial accumulator value is an empty object {}
const countTags = tags => tags.reduce(getTagStats, {});
const tagCount = countTags(tags);
console.log(tagCount);