Maneuvering with Javascript Reduce

Alfred Ayi-bonte
mPharma Product & Tech Blog
6 min readJun 12, 2018

Reduce is one of the most powerful built-in array functions among map, filter, every, some, etc. available in functional programming. Newbies usually overlook its power and resort to external libraries which usually bloat their apps. This piece is to help newbies take advantage of the power of the reduce function. We shall go through a few challenges/exercises in order to make things clear.

Let’s start by first defining reduce.

What is Reduce?

Reduce is a function that takes a function and an initial value as arguments and transforms an array of elements.

Stated differently, reduce can be defined as a method which takes two parameters namely:

  1. A function / callback

2. and an initial value (optional)

It is applied on arrays to accumulate values returned by the callback.

In summary

array.reduce(callback, initialValue)

Let’s talk about the arguments.

InitialValue

InitialValue is the initial value you pass to the reduce method. Usually, initialValue is set to 0 when we want to find the summation of numbers in a list. In the case of creating an object of objects, you may want to initialize your initial value to an empty object. That said, initial value does not have to always be zero when you are summing, or an empty object when you want to create an object of objects. The initial value depends on what you want to achieve in the end.

Suppose I have an amount say 50 and I want to add to the sum of items in the list [3,4,5]. This can be done by setting the initial value to 50 as shown below

const li = [3, 4, 5]
const callback = (accumulator, currentValue) => accumulator + currentValue
const total = li.reduce(callback, 50)
console.log(total) //62

It’s fine if you don’t understand what callback does, as we shall take a look at the Callback in the next section.

Callback

The callback is the function that takes the accumulator, currentValue, currentIndex, and the array.

Note: The order of arguments matter as that affects computation.

function(accumulator, currentValue, currentIndex, array)

Accumulator

The accumulator accumulates the callback’s return values; it is the accumulated value previously returned in the last invocation of the callback, or initialValue if supplied.

CurrentValue

The current element being processed in the array

CurrentIndex (optional)

The index of the current element being processed in the array. Starts at index 0, if an initialValue is provided, and at index 1 otherwise.

Array (optional)

The array reduce() was called upon.

Enough of the talking. Let’s take quick examples.

We shall start with a simple challenge.

Our first Challenge

From the payload below, given that

Revenue = selling price * quantity

We want to find the total of all the revenues.

const payload = [
{
"id": "5b0fcebcb4fea40a51e66ef1",
"item": "orange",
"type": "fruit",
"selling_price": 20,
"cost_price": 5,
"quantity": 4
},
{
"id": "5b0fcebce0cbd5c82feefe03",
"item": "rice",
"type": "cereal",
"selling_price": 25,
"cost_price": 5,
"quantity": 10
},
{
"id": "5b0fcebc02e2e05596d32645",
"item": "maize",
"type": "cereal",
"selling_price": 10,
"cost_price": 10,
"quantity": 2
},
{
"id": "5b0fcebc4c4f4723ff70df07",
"item": "banana",
"type": "fruit",
"selling_price": 15,
"cost_price": 15,
"quantity": 8
},
{
"id": "5b0fcebcab68c47c915b6c3d",
"item": "millet",
"type": "cereal",
"selling_price": 20,
"cost_price": 5,
"quantity": 4
}
]

To solve this sort of puzzle, we first have to ask ourselves if we have to have an initialValue. If yes, what should our initialValue be?

The identity for addition is 0, which means any number added to zero gives the number itself. Therefore to do any sort of addition you need to have 0 as your initialValue. The code below shows how to calculate Total Revenue using reduce.

const TotalRevenue = payload.reduce(
// callback starts
(accumulator, currentValue) => {
//currentValue is each object in the array
const revenue = accumulator + currentValue.selling_price * currentValue.quantity
return revenue;
}, //end of callback

0 //initialValue
)

The table below illustrates the results

From the table above, we see that in the first iteration, the accumulator is 0 because our initialValue is 0, selling price is 20 and quantity is 4. Therefore our new accumulated value which is found in the revenue column is calculated by

const revenue = 0 + 20 * 4 = 80

On the next row, our new accumulated value becomes 80. That 80 added to the product of the selling price and quantity gives our next accumulated value. This continues until the iteration is over, then we have our final answer, which is 550.

Note that accumulated value on the first iteration always takes the value of the initial value.

For our next challenge, we want to be able to return an object of the sum of the revenues and the sum for the costs.

Given that

TotalCost = cost_price * quantity

The code for this challenge is as shown below

const Total = payload.reduce(
// callback starts
(accumulator, currentValue) => {
//currentValue is each object in the array
//revenue calculation
accumulator.TotalRevenue = accumulator.TotalRevenue + currentValue.selling_price * currentValue.quantity
//cost calculation
accumulator.TotalCost = accumulator.TotalCost + currentValue.cost_price * currentValue.quantity
return accumulator;
}, //end of callback
//initialValue starts
{
"TotalRevenue": 0,
"TotalCost": 0
} //end of initialValue
)

The table for this solution is similar to that of the previous one with the addition of a new column for our cost.

Now to calculate our profit or loss, we can have

const profit_loss = Total.TotalRevenue - Total.TotalCost

A negative answers means a loss and positive answers means we made profit.

Next up, We want to be able to create group for cereal and fruit and be able to easily access them in our React components.

The code below demonstrates how we can achieve that.

const result = payload.reduce(
// callback starts
(accumulator, currentValue) => {
//currentValue is each object in the array
//revenue calculation
accumulator.TotalRevenue = accumulator.TotalRevenue + currentValue.selling_price * currentValue.quantity
//cost calculation
accumulator.TotalCost = accumulator.TotalCost + currentValue.cost_price * currentValue.quantity
accumulator.allIds.push(currentValue.id)
accumulator.byId[currentValue.id] = currentValue
if(currentValue.type === 'cereal') accumulator.cereal.push(currentValue.id) ;
if(currentValue.type === 'fruit') accumulator.fruit.push(currentValue.id) ;
return accumulator;
}, //end of callback
//initialValue starts
{
"TotalRevenue": 0,
"TotalCost": 0,
"allIds": [],
"byId": {},
"cereal": [],
"fruit": []
} //end of initialValue
)
console.log(result)

Output

{
TotalCost: 230,
TotalRevenue: 550,
allIds: ["5b0fcebcb4fea40a51e66ef1", "5b0fcebce0cbd5c82feefe03", "5b0fcebc02e2e05596d32645", "5b0fcebc4c4f4723ff70df07", "5b0fcebcab68c47c915b6c3d"],
byId: {
5b0fcebc02e2e05596d32645: {
cost_price: 10,
id: "5b0fcebc02e2e05596d32645",
item: "maize",
quantity: 2,
selling_price: 10,
type: "cereal"
},
5b0fcebc4c4f4723ff70df07: {
cost_price: 15,
id: "5b0fcebc4c4f4723ff70df07",
item: "banana",
quantity: 8,
selling_price: 15,
type: "fruit"
},
5b0fcebcab68c47c915b6c3d: {
cost_price: 5,
id: "5b0fcebcab68c47c915b6c3d",
item: "millet",
quantity: 4,
selling_price: 20,
type: "cereal"
},
5b0fcebcb4fea40a51e66ef1: {
cost_price: 5,
id: "5b0fcebcb4fea40a51e66ef1",
item: "orange",
quantity: 4,
selling_price: 20,
type: "fruit"
},
5b0fcebce0cbd5c82feefe03: {
cost_price: 5,
id: "5b0fcebcb4fea40a51e66ef1",
item: "rice",
quantity: 10,
selling_price: 25,
type: "cereal"
}
},
cereal: ["5b0fcebce0cbd5c82feefe03", "5b0fcebc02e2e05596d32645", "5b0fcebcab68c47c915b6c3d"],
fruit: ["5b0fcebcb4fea40a51e66ef1", "5b0fcebc4c4f4723ff70df07"]
}

Suppose we want to display all our cereal in a React component that has result passed as props we can easily do that by

import React from 'react';
const CerealComponent = ({ result }) => (
<ul>
{
result && result.cereal.map(
(id) => <li key={id}>{ result.byId[id].item }</li>
)
}
</ul>
)
export default CerealComponent

Observation

By using the reduce method in these scenarios, we have been able to avoid lodash’s mapKeys which could have been used to attain the structure in our byId.

Also, you realize that we’ve been able to group our cereal and fruit without having to use any sort of library.

Avoiding lodash’s flatten

To demonstrate this, we shall use a different payload

const payload = [['a', 'b'], ['c', 'd'], ['e', 'f']]

The code below demonstrates how reduce can be used to transform the above payload to ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

const payload = [['a', 'b'], ['c', 'd'], ['e', 'f']]
const flattenedArray = payload.reduce( (accumulator, currentValue) => accumulator.concat(currentValue), [])

From the above code snippet, our initial value is an empty list because that’s the identity for list concatenation. The concat help join lists together so that by the end of the iterations our list of lists becomes one list.

For Deep flattening of a list we will need to apply some sort of recursion in our reduce method to get our final result

const payload = ['A',['B', 'C'], ['D', ['E', ['F']]]]
const flattenArray = (list) =>
Array.isArray(list)
?
list.reduce(
// reduce callback
(accumulator, currentValue) => [...flattenArray(accumulator), ...flattenArray(currentValue)],
//reduce initialValue
[]
)
:
[list]
const flattenedArray = flattenArray(payload)

There’s much more we can do with reduce but I will pause here for questions. You can either reply to this post or send me a mail via alfredayibonte@gmail.com, and I will respond to you.

Reference

Mozilla

--

--