More JS. reduce()/spread syntax
Further Study: reduce() and spread syntaxPermalink
Solving some exercises, I found it hard to get the idea of using
reduce()
. Either the explanation in 30DaysOfJs or the MDN Document didn’t help me, so I decided to study more about the method with some video tutorials.
1. Understanding the conceptPermalink
reduce()
takes two arguments:
1) a callback function with two parameters: accumulator and current value
2) the initial value for the accumulator(optional but recommended - the default value is set to the value of first index).
The callback function is executed multiple times, and each time the current value will be set to one element in the array, moving forwards. So each call, I’ll get the current value and execute the function. Internally, the reducer will get the result of the function and store it in the accumulator. Finally, it returns a single value, and I can store it in a variable.
So to simply put, reduce()
takes two parameters, executes a function on those parameters, and the result from the function becomes one of the parameters on the next round. The accumulator is continuously set to the value after the callback function is executed. The reducer starts with an accumulator, and looping through the array, converts all the elements into a single value.
//without initialization, adding all the elements of numbers array
const sum = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
+ Spread Syntax(…)Permalink
Spread syntax “expands” an array into its elements. can be used when all elements from an object or array need to be included in a list of some kind. When I invoke a function, I can pass all the values in the array by using the spread syntax and the array name - ...array
It is commonly used when I want to add a new item to a local data store, or display all stored items plus a new addition.
let numbers = [0, 1, 2];
let newNumber = 3;
numbers = [...numbers, newNumber];
console.log(numbers); // (4) [0, 1, 2, 3]
Spread syntax could also be used to combine two arrays by inserting all elements from the array.
let array = [0, 1, 2];
let newArray = [...array, 3, 4, 5];
console.log(array); // (6) [0, 1, 2, 3, 4, 5]
2. How to use reduce() with examplesPermalink
const people = [
{ id: "1", name: "Leigh", age: 35 },
{ id: "2", name: "Jenny", age: 30 },
{ id: "3", name: "Heather", age: 28 }
]
let result;
1) Count the numbers of itemsPermalink
result = people.reduce((acc, person) => acc + 1, 0);
console.log(result); // 3
2) Sum all agesPermalink
result = people.reduce((acc, person) => acc + person.age, 0);
console.log(result); // 93
3) Print the array of names (map)Permalink
To add new elements in the array, I can use the spread syntax(...
).
result = people.reduce((acc, person) =>
[...acc, person.name] , []);
console.log(result); // (3) ['Leigh', 'Jenny', 'Heather']
4) Convert to id => person lookup (dict)Permalink
In this case, I want to make an array of objects that has id: info as a key: value pair. This will make the searching easier. It’s more efficient than looping all through the array.
result = people.reduce((acc, person) => {
return {...acc, [person.id]: person}
}, {})
console.log(result);
/* {1: {id: '1', name: 'Leigh', age: 35},
2: {id: '2', name: 'Jenny', age: 30},
3: {id: '3', name: 'Heather', age: 28}} */
console.log(result['2']);
// 2: {id: '2', name: 'Jenny', age: 30}
5) Print out the max agePermalink
Before, I would make a new array of number values using map()
, then use Math.max()
to get the maximum number. Using reduce()
is much efficient than that. For the initial value, null
is used instead of 0. It’s because the numbers could be negative values, in which 0 would be the maximum.
result = people.reduce((acc, person) => {
if (acc === null || acc < person.age)
return person.age;
return acc;
}, null)
console.log(result); // 35
If the value hasn’t settled(acc === null
) or if the current value is larger than the value in the accumulator(acc < person.age
), it will return the current value(person.age
). If not, the previous value(acc
) will be returned.
6) Print out the min agePermalink
result = people.reduce((acc, person) => {
if (acc === null || acc > person.age) return person.age;
return acc;
}, null)
console.log(result);
7) Find an item by name (find)Permalink
result = people.reduce((acc, person) => {
if (acc != null) return acc;
// I haven't found the right person yet
if (person.name === "Leigh") return person;
// keep looking(going back to line 1)
return null;
}, null)
console.log(result);
// {id: '1', name: 'Leigh', age: 35}
① if (acc != null)
is to check if I already found someone.
-
acc != null
means that I already found someone. It will returnacc
, which is the person’s info(the element). -
If I haven’t found the person from the previous loop (if
acc
isnull
), the code will get to the next line.
② if(person.name === "Leigh")
checks if the current value is the person I’m looking for(“Leigh” in this case).
-
If the person is the one I’m looking for, it returns that person’s info(the element).
-
If not, the code will get to the next line, return
null
(it’ll keep looking) and go back to ① for the next loop. If a person is not found, the result would benull
.
8) Check if all are over 18 (every)Permalink
result = people.reduce((acc, person) => {
if (!acc)
return false;
return person.age > 18;
}, true)
console.log(result); // true
In this case, the initial value is set to true
. I first assume all are over 18 unless I find one that isn’t, in which case I’ll flip the value over to false
.
① Starting the loop, acc
is set to true
. !acc
is false, so the code will get to the next line and return the Boolean value of person.age < 18
.
② Check if at least one person is under 18
-
If a person is under 18,
false
would be returned at the end of the loop. Starting a new loop, the condition!acc
is true and the reducer will returnfalse
. It means that there are at least one person under 18. -
If the person is over 18,
true
would be returned and the code will get to the next loop.
9) Check if any over 18 (some)Permalink
result = people.reduce((acc, person) => {
if (acc) return true;
return person.age > 18;
}, false)
In this case, the initial value is set to false
. I first assume all are not over 18. As soon as I find someone over 18, I’ll flip the value over to true
. The initial value is set to false
, so I have to set the condition acc
to check if anyone is over 18.
- Things to notice
reduce()
loops through the whole array, so I have to consider how the very first loop and the end of the previous loop would be like. Especially when putting some logic(if) in the code. The callback function doesn’t end just executed once. One more, the code inside if
is executed only if the condition inside its parentheses is true
.
const orders = [
{ id: "1", status : "pending" },
{ id: "2", status : "pending" },
{ id: "3", status : "cancelled" },
{ id: "4", status : "shipped" }
];
10) Count occurencesPermalink
In this case, I want to make an array of objects that has status: times as a key: value pair.
result = orders.reduce((acc, order) => {
return {...acc, [order.status]: (acc[order.status] || 0) + 1 };
}, {})
console.log(result);
// {pending: 2, cancelled: 1, shipped: 1}
The value will be (whatever it was before) + 1. If I have that status before, it’ll take existing value(acc[order.status]
) and add 1 to that value. acc[order.index]
will return the times value from the acc
. If I see the status for the first time, the value will be initialized to 0.
const folders = [
"index.js",
["flatten.js", "map.js"],
["any.js", ["all.js", "count.js"]]
];
11) Flatten an array when dealing with nested data structuresPermalink
I can think of folders
array as a folder that has a file or another folder that contains another files. In this case, I want to get a single array of only files.
function flatten(acc, item) {
if (Array.isArray(item)) {
return [...acc, ...item.reduce(flatten, [])];
}
return [...acc, item];
}
result = folders.reduce(flatten, []);
console.log(result);
// (6) ['index.js', 'flatten.js', 'map.js', 'any.js', 'all.js', 'count.js']
In this case, a function to be called recursively is needed. The folders
array will be reduced with flatten
function. The function gets two parameters: acc
and item
(the current value). As I can see, item
could be a string or an array.
The if
condition checks whether the item is an array or not.
-
If an item is a string,
Array.isArray(item)
isfalse
, and the code will get to the next line. It will just add the item toacc
. -
If an item is an array,
Array.isArray(item)
istrue
, the function recursively calls itself and flattens all of its children. Then the elements of the array will be expanded by spread syntax(...
) and added to the array.
The function can be simplified as below.
function flatten(acc, item) {
if (Array.isArray(item)) {
return item.reduce(flatten, acc);
}
return [...acc, item];
}
result = folders.reduce(flatten, []);
console.log(result);
// (6) ['index.js', 'flatten.js', 'map.js', 'any.js', 'all.js', 'count.js']
It uses the same function, but this time the starting value is whatever I’m currently at(acc
). This way I can shorten the code.
12) Create reduce myself (instead of using reduce)Permalink
function reduce(array, callback, initial) {
let acc = initial;
for (let i = 0; i < array.length; i++) {
acc = callback(acc, array[i], i, array)
}
return acc;
}
result = reduce([1, 2, 3], (acc, num) => acc + num, 0);
console.log(result); // 6
I can make a reduce function, which does the same job as reduce()
method. The function gets three parameters: the array, the callback function, and the initial value.
First, I create a variable to store the accumulator and initialize the value. It works just like acc
. Then, I use for loop
to actually loop through all the elements in the array. While looping, the accumulator is updated(or replaced) to whatever the callback returns.
The callback function expects to receive the current accumulator, current element that I’m iterating over(array at the index), current index(optional), and the entire array(optional). After the loop, the function returns acc
value.
On the upper code, the array is [1, 2, 3]
, the callback function is (acc, num) => acc + num
, and the initial value is 0. The elements of the array will be added through the for loop.
In the case of find(7), every(8), any(9), reduce()
can be efficient in that I can actually stop the loop. If I get a value that I’m looking for, there’s no point to continue iterating the rest of the array.
Leave a comment