Declutter Array.reduce()

Many people say using Reduce method in your code makes it messy. Hence many experts advise avoiding it as much as possible. But leave all those advice aside. We will make sense now. Remember, it is still a built-in method for Arrays in Javascript.


Reduce = Many to one. It is used on arrays. So here, many refer to elements in the array, and one refers to the output(result). This output is given by Reduce method.

Reduce is a method that takes in one callback function and initial value.

Array.reduce(() => {} , initialValue) --> Callback and initial value

This initial value decides the output or result or single(one) final value.

If,

Initial value = number, then output is a single Number.
Initial value = Object {}, then output is a single Object.
Initial value = Array [], then output is a single Array.

We are dealing with arrays, we do operations on array elements. Each operation will give you a result. Do we need that result? Pause and ponder!


Of course yes! But why? Because we are converting many elements into one element. So each operation is based on the result of the previous element's operation. Sounds confusing, right?

Remember: At the first iteration, we don't have the previous iteration result, Therefore, we give the initial value.


Where does the operation take place?

Inside the callback function(Function contains operations and variables to perform). The function must use previous result values, so we need a variable to store the result. From this result, subsequent operations are done. This variable is the first parameter of the callback function popularly called 'Accumulator'.

Since we work on arrays, the second and third parameters would be a current element in the array and index, respectively—enough of a boring description. Let's see a classic example.


const arr = [1,2,3]

// Syntax
// Array.reduce(() => {} , initialValue) --> Callback and initial value

//Callback parameters --> accumulator(previous result),current element in a array, index.

// We will add all the elements in an array with an initial value of 0
const reduced = arr.reduce((acc,elem,index) => {
    console.log('acc',acc)
    console.log('elem',elem)
    console.log('index',index)
    return acc + elem
},0)

console.log('The final output is',reduced)

// Output is
acc 0 --->First iteration value will be initial value
elem 1
index 0

acc 1 --> 0+1(Form previous result) --> acc + elem
elem 2
index 1

acc 3 -->  1+2(From previous result) --> acc + elem
elem 3
index 2

The final output is 6 --> Result of the output is equal to the final accumulator value(3+3) because no element to process further in the array.

Let's change the initial value,



const arr = [1,2,3];
const reduced = arr.reduce((acc,elem) => {
    return acc + elem
},100)

console.log('The final output is',reduced)

// Prints value 106 
because when it enters the callback function for the first iteration, it will have acc value of 100 = initial value

Note:

  • On the first iteration, acc = initial value. So both are compulsory.

  • Then the second parameter in the callback function is the current element in the array it is going to process.

Let us complicate a bit, say an array of objects with the property name 'Price'. We need the total price.

const arr = [
{
price:100,
},
{
price:200,
},
{
price:300,
},
];

const reduced = arr.reduce((acc,elem) => {
return acc + elem.price
},0)

console.log('The total value is',reduced)
// Outputs --> The total value is 600

For nested objects,

const arr = [
{
product:{
name:'Shirt',
price:2000
}
},
{
product:{
name:'Pant',
price:1000
}
},
{
product:{
name:'Shoe',
price:1500
}
},
];

const reduced = arr.reduce((acc,elem) => {

let {product} = elem; --> Destructuring first level
return acc + product.price

},0)

console.log('The total value is',reduced)
// Outputs --> The total value is 4500

Let's do minimum and maximum in an array,

We will change the initial maximum to the first element in an array and then it checks with every element in an array.

const arr = [100,30,9,0,200,90,10,25]


const arr = [100,30,9,0,200,90,10,25];

const reduced = arr.reduce((acc,elem,index) => {
// only enters if the current maximum(acc) is less than current element
 if(acc<elem) {
 acc = elem
 return acc
 }
 // enters current maximum is greater
 else {
  return acc;
 }
 },arr[0]) // First value of array
 
 console.log(reduced)
 
 //Prints 200

For minimum,

const arr = [100,30,9,0,200,90,10,25];

const reduced = arr.reduce((acc,elem,index) => {
// only enters if the current minimum(acc) is greater than current element
 if(acc>elem) {
 acc = elem
 return acc
 }
 // enters current maximum is greater
 else {
  return acc;
 }
 },arr[0]) // First value of array
 
 console.log(reduced)
 
 // Prints 0

We will reduce an array of objects,

Typical e-commerce problem, No of quantities each item is added into the cart.


const arr = [
{
product:{
name:'Shirt',
price:2000,
}
},
{
product:{
name:'Pant',
price:1000
}
},
{
product:{
name:'Shoe',
price:1500
}
},
{
product:{
name:'Shoe',
price:1500
}
},
{
product:{
name:'Pant',
price:1000
}
},
{
product:{
name:'Pant',
price:1000
}
},
{
product:{
name:'Shirt',
price:2000,
}
},
];

Let's take a closer look at the structure.

Is it an array? Yes. So we can reduce it!

What is the structure? An array of objects.

Do we have our intended properties at the first level? No,it is nested.

What is the complete structure?

[
{
product:{
    name:...,
    price:...
    }
}
]

What do we need to find? The number of times each product appears in this array.

The final output should be in Object with key as the name of the product and value of the number of times it appeared.

We use dynamic key computation and spread operator because every iteration checks the key and retains already computed properties and values. This will be stored in the accumulator for the next iteration and the last iteration result will be output.

Checking keys = Dynamic keys.

Retaining the results of previous output in an object = Spread operator.


const reduced = arr.reduce((acc,elem) => {

let {product} = elem;

if(!acc[product.name]) {
// using dynamic keys [product.name] and spread operator (...acc)
return {...acc,[product.name]:1}
}

else {
return {...acc,[product.name]: acc[product.name] + 1}
}

},{}) 
// Final output is reduced to single object and hence initial value is empty object

Let me explain the logic here,

If the key is not available in output object --> if(!acc[poduct.name]).

Note: At first iteration --> it will be empty object {}

Please copy already computed values(key and values) and initialize a new key with the value of '1'.

= {

...acc,

[product.name]:1

}

Else,

Copy already computed values(key and values) and add a particular key with '1' =

{

...acc,

[product.name]: acc[product.name] + 1

}


So the output is,

{Shirt:1, Pant:3, Shoe:2}

Let's add the product name into a single array.


const reduced = arr.reduce((acc,elem) => {
   let {product} = elem;
   
   // Since we initialize it as an array,we can use push method
   acc.push(product.name)
   
   return acc
},[])

console.log(reduced)
//[ 'Shirt', 'Pant', 'Shoe', 'Shoe', 'Pant', 'Pant', 'Shirt' ]

Since we can create another array from the original array like above, we can do map operations over an array using reduce.Let us square each element in an array.

const arr = [5,10,15,20,25];

const reduced = arr.reduce((acc,elem) => {
    let squared = elem * elem
    acc.push(squared)
    return acc
},[])

console.log(reduced)

The output is

[ 25, 100, 225, 400, 625 ]

Similarly, we can filter an array using reduce,

const arr = [5,10,15,20,25];

const reduced = arr.reduce((acc,elem) => {
if(elem > 15) {
acc.push(elem)
return acc
}
else {
return acc
}
},[])

The output is

[ 20, 25 ] // Values greater than 15