Photo by Esteban Lopez on Unsplash
reduce
.
reduce
method
reduce
method executes a
reducer
callback function (provided by the user) on each element of the array, resulting in a single output value.
reducer
reducer
function takes four arguments:
- Accumulator (acc)
- Current value (cur)
- Current index (idx)
- Source array (src)
reducer
function’s return value is assigned to the accumulator, whose value is remembered across each iteration throughout the array and ultimately becomes the final, single resulting value.
undefined
.
initialValue
reduce
method takes a second optional argument:
initialValue
.
If not provided, the initial value of the accumulator will be the first element of the array, and the first iteration will point to the second element. If
initialValue
is provided, it will be the initial value of the accumulator, and the first iteration will point to the first element of the array.
Summing numbers with/without an initial value
const numbers = [1, 2, 3];
// Without initialValue
const sum = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
// Prints 6
console.log(sum);
// With initialValue
const initialValue = 3;
const sumWithInitialValue = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
, initialValue);
// Prints 9
console.log(sumWithInitialValue);
accumulator
pointing to the first element of the array (1), and
currentValue
pointing to the second element of the array (2).
accumulator
with the value of the given initial value (3), and
currentValue
will point to the first element of the array (1).
Counting the number of occurrences in an array
Let’s count the number of occurrences of words in the following and store the results in a map:
How much wood would a woodchuck chuck
If a woodchuck could chuck wood?
He would chuck, he would, as much as he could,
And chuck as much as a woodchuck would
If a woodchuck could chuck wood.
const sentence = "how much wood would a woodchuck chuck" +
"if a woodchuck could chuck wood " +
"he would chuck he would as much as he could " +
"and chuck as much as a woodchuck would " +
"if a woodchuck could chuck wood";
const words = sentence.split(" ");
const occurencesMap = words.reduce(
(occurences, word) => {
const numOfOccurences = (occurences.get(word) || 0) + 1;
occurences.set(word, numOfOccurences);
return occurences;
}
, new Map());
const numOfWoodchucks = occurencesMap.get("woodchuck");
// 4
console.log(numOfWoodchucks);
We initialize an empty map and use it as the initial value of the accumulator, initializing or updating the number of occurrences of each word as we iterate over the words in the sentence.
reduce
is, right?
It allows you to take an array and reduce its values to basically anything that can be derived from the data it holds. It also allows you to return any type of data, regardless of the type of the elements of the array.
Revisiting other ES5 array methods, we can see that each method uses the given callback function on the array and returns some kind of result.
For example:
-
map
transforms each element of the array, returning a new array.
-
every
checks if the given condition applies to every element in the array, returning the corresponding Boolean value.
Looks familiar, right?
reduce
to implement other ES5 array methods.
Array
’s prototype in each example, where
this
will point to the array on which we’re operating.
transformer
callback.
Usage
const array = [1, 2, 3];
const doubled = array.map(num => num * 2);
// Prints [2, 4, 6]
console.log(doubled);
Using a transformer callback that doubles every number in the array, we get a new array where every element is twice its original value.
With
reduce
map
operates on an array and returns a new array, so the accumulator has to be an array.
Array.prototype.mapWithReduce = function(transformer) {
return this.reduce((newArray, currentElement) => {
const newElement = transformer(currentElement);
newArray.push(newElement);
return newArray;
}, []);
}
const array = [1, 2, 3];
const doubled = array.mapWithReduce(num => num * 2);
// Prints [2, 4, 6]
console.log(doubled);
reduce
, we start with an empty array accumulator and iterate over the array. We then apply the transformer callback on each element and push it to the accumulating array.
Usage
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenOnly = array.filter(num => num % 2 === 0);
// Prints [2, 4, 6, 8, 10]
console.log(evenOnly);
Using a test callback that filters out all odd numbers, we get a new array with all the even elements of the original array.
With
reduce
Just like the previous example, filter also operates on an array and returns a new array, so the accumulator has to be an array.
Array.prototype.filterWithReduce = function(tester) {
return this.reduce((newArray, currentElement) => {
if (tester(currentElement)) {
newArray.push(currentElement);
};
return newArray;
}, []);
}
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenOnly = array.filterWithReduce(num => num % 2 === 0);
// Prints [2, 4, 6, 8, 10]
console.log(evenOnly);
reduce
, we start with an empty array accumulator and iterate over the array. We then use the tester callback to check if each element should be pushed to the accumulating array.
Usage
const array = [1, 2, 3, 4, 5];
const result = array.every(num => num < 10);
// Prints true
console.log(result);
every
returns
true
.
With
reduce
every
operates on an array and returns a Boolean value, so the accumulator has to be a boolean.
Array.prototype.everyWithReduce = function(tester) {
return this.reduce((acc, currentElement) =>
acc && tester(currentElement)
, true);
}
const array = [1, 2, 3, 4, 5];
const result = array.everyWithReduce(num => num < 10);
// Prints true
console.log(result);
reduce
, we start with a boolean accumulator value of
true
(we’ll discuss the reason later on) and iterate over the array. We then chain the result of the tester callback to the accumulator using the logical AND (
&&
), to eventually return
true
if all elements pass the test, and false
otherwise
.
true
?
every
returns
true
regardless of the test callback (even if the callback returns
false
).
Else, if all elements fulfill the condition, the chaining of the initial
true
value using the logical AND will eventually resolve to
true
. If not, the chaining will eventually resolve to
false
.
Usage
const array = [1, 2, 3, 4, 5];
const result = array.some(num => num > 3);
// Prints true
console.log(result);
some
returns
true
.
With
reduce
some
operates on an array and returns a boolean value, so the accumulator has to be a boolean.
Array.prototype.someWithReduce = function(tester) {
return this.reduce((acc, currentElement) =>
acc || tester(currentElement)
, false);
}
const array = [1, 2, 3, 4, 5];
const result = array.someWithReduce(num => num > 3);
// Prints true
console.log(result);
reduce
, we start with a boolean accumulator value of
false
(we’ll discuss the reason later on) and iterate over the array. We then chain the result of the tester callback to the accumulator using the logical OR (
||
), to eventually return
true
if any element passes the test, and
false
otherwise.
false
?
some
returns
false
regardless of the test callback (even if the callback returns
true
).
false
value using the logical OR will eventually resolve to
true
. If not, the chaining will eventually resolve to
false
.
every
method executes the provided callback function once for each element present in the array until it finds the one where callback returns a falsy value (a value that becomes
false
when converted to a boolean). If such an element is found,
every
immediately returns
false
.
some
method executes the callback function once for each element present in the array until it finds the one where callback returns a truthy value (a value that becomes
true
when converted to a boolean). If such an element is found,
some
immediately returns
true
.
reduce
mid-loop.This means that while both implementations (both the original
every/some
method and the corresponding implementations using
reduce
) have a runtime of
O(n)
, the original implementations are likely to terminate without having to iterate over the entire array, making them more efficient.
Given an array of numbers, what is the most efficient way to filter out all even elements and square the remaining ones (using ES5 methods)?
filter
followed by
map
:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const tester = num => num % 2 === 1;
const transformer = num => num * num;
const result = array.filter(tester).map(transformer);
// Prints [1, 9, 25, 49, 81]
console.log(result);
filter
and
map
methods, and return the desired array.
filter
and
map
with
reduce
, only this time let’s combine them in one go.
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const tester = num => num % 2 === 1;
const transformer = num => num * num;
const result = array.reduce((newArray, currentElement) => {
if (tester(currentElement)) {
const newElement = transformer(currentElement);
newArray.push(newElement);
}
return newArray;
}, []);
// Prints [1, 9, 25, 49, 81]
console.log(result);
We use the same tester and transformer functions to test if each element should be kept in the array and transform it if it should.
This approach saves us the need to create an intermediate array of filtered values, and we get a slightly more efficient algorithm as we don’t have to iterate over two different arrays (the original and the intermediate).
reduce
method to implement other ES5 methods.
How do you use it in your day-to-day coding? Share in the comments!