Advanced Data Manipulation in Javascript
This is not a beginner tutorial on Map, Reduce and Filters. You should already be somewhat familiar with the concepts — at least the basics. Lets get right to it.
Data from an endpoint
Say you have data coming from an endpoint in the following form
[
{
mood: "happy",
fish: "robin",
colours: ["blue", "green"]
},
{
mood: "tired",
fish: "panther",
colours: ["green", "black", "orange", "blue"]
},
{
mood: "sad",
fish: "goldfish",
colours: ["green", "red"]
}
]
Its an array of objects. Lets say you want to obtain the list of all the colours. How would you approach this problem?
For an intermediate developer, this is a piece of cake.
Create a myColours
Array. Loop over the response from the endpoint and push the colours property (which is an array) into our myColours
Array.
Translated to javascript:
var data = [
{
mood: "happy",
fish: "robin",
colours: ["blue", "green"]
},
{
mood: "tired",
fish: "panther",
colours: ["green", "black", "orange", "blue"]
},
{
mood: "sad",
fish: "goldfish",
colours: ["green", "red"]
}
]var myColours = [];
for(let i = 0; i< data.length; i++){
myColours.push(data[i].colours);
}
console.log(myColours) ===>[
[“blue”, “green”],
[“green”, “black”, “orange”, “blue”],
[“green”, “red”]
]
However, as you get more experienced, you realise the importance of having your codebase follow some specific patterns and rules. As the developer team grows, you need to implement some of these rules in order to keep the codebase very consistent for a new developer to adapt quickly to the project. Also it is almost (not always) better to find the solution with the least lines of code by not sacrificing readability. This is why more advanced developers use javascript functions such as map, filter and specially reduce. They make you write less lines of code in an orderly fashion that can be easily read and maintained.
Fewer lines means fewer bugs
Lets see how we can use map to solve the same problem
var myColours = colours.map((e)=>{
return e.colours
})
console.log(myColours)[
[“blue”, “green”],
[“green”, “black”, “orange”, “blue”],
[“green”, “red”]
]
Now what if I want to flatten the array. I want a single array with all the colours. Again the naive solution to this is
var flattenedArray = [];
var coloursArray = [
[“blue”, “green”],
[“green”, “black”, “orange”, “blue”],
[“green”, “red”]
]for(let i =0; i<coloursArray.length; i++){
for(let j=0; j< coloursArray[i].length;j++){
flattenedArray.push(coloursArray[i][j]);
}
}
console.log(flattenedArray)["blue", "green", "green", "black", "orange", "blue", "green", "red"]
How to improve this solution? We can use reduce and concat.
var flattenedArray = [];
var coloursArray = [
[“blue”, “green”],
[“green”, “black”, “orange”, “blue”],
[“green”, “red”]
]flattenedArray = coloursArray.reduce( (total, subArray)=>{
return total.concat(subArray)
},[]);
console.log(flattenedArray)["blue", "green", "green", "black", "orange", "blue", "green", "red"]
Now what if you want only the unique elements in the array. You should memorise this solution actually as it will come handy all the time and its a very common interview question that.
var array = [“blue”, “green”, “green”, “black”, “orange”, “blue”, “green”, “red”];
var uniqueArray = [];uniqueArray = array.filter((element, index, array)=>{
return array.indexOf(element) === index;
})
console.log(uniqueArray)["blue", "green", "black", "orange", "red"]
Lets back up for a second now. We get the response from the backend API.
Now I want the unique colours from that data. What I would do is to create three functions that are generic functions. They can be used in other places as well — Helper functions.
function getColoursFromArray(array){
return array.map( e =>{
return typeof e.colours !== ‘undefined’ && e.colours
})
}function flattenArray(array){
return array.reduce((total, next)=>{
return total.concat(next)
},[])
}function getUniqueItems(array){
return array.filter((e, i, self)=>{
return self.indexOf(e) === i
});
}var pipeline = [getColoursFromArray, flattenArray, getUniqueItems]var result = pipeline.reduce( (total, func)=>{
return func(total)
},data)console.log(result)["blue", "green", "black", "orange", "red"]
Here you can see a cool feature of reduce, in which you can create a pipeline of functions and pass some data through those functions sequentially, passing the output of one function onto the next one. Its pretty handy and very readable.
In a second you know exactly what is happening with the data.
We first extract the colours, then flatten it, and finally get the unique items.
If you think this is more verbose, then think again because the three helper functions I wrote can be used elsewhere in the codebase easily. You can put them in a separate class or a file and import them when needed.
When you need order, use reduce
Reduce is good when you wanna do things in a sequence. Lets say you have an async function and you want to call it three times but in a sequence. There are many ways to approach this problem. However, with reduce, we save lines of code, increase readability and maintain order.
function asyncFunc(e) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(e), e * 1000);
});
}
var arr = [1, 2, 3];
var final = [];
function process(arr) {
return arr.reduce((promise, item) => {
return promise
.then((result) => {
return asyncFunc(item).then(result => final.push(result));
})
.catch(console.error);
}, Promise.resolve());
}
process(arr)
.then(() => console.log('FINAL RESULT => ' + final ));
This piece of code will log
FINAL RESULT => 1,2,3
after 1 + 2 + 3 = 6 seconds.
You can use Promise.resolve() as a starting point in your reduce function and then return a promise each time in the loop.
You can add async and await to the mix to make it look more readable. But thats for an other day.
Code it yourself
To understand Reduce better, see this snippet
Array.prototype.reduce = function(callback, initialValue) {
var resultSoFar, i
var n = this.length
if (typeof initialValue !== 'undefined') {
resultSoFar = initialValue
i = 0
} else {
resultSoFar = this[0]
i = 1
}
for (; i < n; i++) {
resultSoFar = callback(resultSoFar, this[i], i, this)
}
return resultSoFar
}
(Taken from https://stackoverflow.com/questions/34303279/reduce-in-depth)
This is not the real implementation but an implementation that is understandable. Sometimes some libraries override the reduce function. (WHAT?). Yes you can do that too. And its bad practice. So you gotta know how to code these simple functions yourself. You learn a thing or two as well.
Cheers!