Functional Programming: Filter, Map and Reduce in JS (Advance).
How to use The pillars of functional programming with examples.
These three functions are part of the main operations on arrays in almost every programming language.
What you need to know before reading this article:
- What is a callback.
- Some familiarity with ES6 syntax.
- Know how to run javascript code using a REPL in Nodejs or the browser.
In this article you will learn:
- A better understanding of these functions.
- Check on complex cases to see their potential.
As a side note, this is not a reference manual to use all the arguments these functions have and it's not an introduction on simple use cases.
To understand how useful they are when working together we'll introduce some data and ask questions. Let us suppose that we are consuming an API from a bookstore that gives us a list of books with attributes such as id
, author
, title
, price
, number of pages
and a category
.
const books = [
{
id: 1,
author: "J.R.R Tolkien",
title: "The lord of the rings, The Fellowship of the Ring",
price: 8.54,
pages: 555,
category: "fiction"
},
{
id: 2,
author: "J.R.R Tolkien",
title: "The lord of the rings, The Two Towers",
price: 8.34,
pages: 467,
category: "fiction"
},
{
id: 3,
author: "J.K. Rowling",
title: "Harry Potter and the Philosopher's Stone",
price: 8.16,
pages: 345,
category: "fiction"
},
{
id: 4,
author: "Lewis Carroll",
title: "Alice in Wonderland",
price: 2.70,
pages: 86,
category: "fiction"
},
{
id: 5,
author: "C.S. Lewis",
title: "The Chronicles of Narnia",
price: 2.99,
pages: 118,
category: "fiction"
},
{
id: 6,
author: "Stephen Hawking",
title: "The universe in a nutshell",
price: 22.93,
pages: 224,
category: "science"
}
]
Filter
Filter is a function that operates over an array of elements and creates a new array with the elements that pass the callback test, that is, when the callback returns true
the element is retrieved in the new array.
This is the syntax:
const callback = (element, index, array) => { /* condition */}
const newArray = arr.filter(callback)
Usually, we only use the element
. Optional arguments are index
and array
.
Let's ask questions and see how filter can answer them:
- Give me all books from J.R.R Tolkien.
- Give me all books that worth less than 5 dollars.
- Give me all books with less than 100 pages.
Give me all books from J.R.R Tolkien.
const tolkiens = books.filter(book => book.author === "J.R.R Tolkien" );
/*
[
{
author: "J.R.R Tolkien",
title: "The lord of the rings, The Fellowship of the Ring",
price: 8.54,
pages: 555,
category: "fiction"
},
{
author: "J.R.R Tolkien",
title: "The lord of the rings, The Two Towers",
price: 8.34,
pages: 467,
category: "fiction"
}
]
*/
Give me all books that worth less than 5 dollars.
const lessThanFive = books.filter(book => book.price <= 5 );
/*
[
{
author: "Lewis Carroll",
title: "Alice in Wonderland",
price: 2.70,
pages: 86,
category: "fiction"
},
{
author: "C.S. Lewis",
title: "The Chronicles of Narnia",
price: 2.99,
pages: 118,
category: "fiction"
}
]
*/
Give me all books with less than 100 pages.
const lessThanAHundred = books.filter(book => book.pages <= 100 );
/*
[
{
author: "Lewis Carroll",
title: "Alice in Wonderland",
price: 2.70,
pages: 86,
category: "fiction"
},
]
*/
Map
Map takes an array of elements and returns a new array of elements transformed by the callback.
This is the syntax:
const callback = (currentValue, index, array) => { /* mapping */}
const newArray = array.map(callback)
index
and array
are optional.
We'll have three examples.
- Give me all the titles, that worth less than 5 dollars.
- Export the data to a CSV file.
- Render an array of object in Reactjs.
To answer the first question we'll use the filter method to satisfy the condition of less than 5 dollars, then we'll sort them by author
using the sort
function that will sort them according to the author's name and finally we'll map the books using the title
and the author
attribute.
const lessThanFive = books
.filter(book => book.price <= 5 )
.sort((first, second) => {
const nameA = first.title.toUpperCase();
const nameB = second.title.toUpperCase();
return (nameA < nameB) ? -1 : 1;
})
.map(book => `${book.author} - ${book.title}`);
/*
[
'Lewis Carroll - Alice in Wonderland',
'C.S. Lewis - The Chronicles of Narnia'
]
*/
How about if we want to export our data to a .csv
file? we could do that like this using fast-csv
. Place the following in an index.js
file.
const fs = require('fs');
const csv = require('fast-csv');
const ws = fs.createWriteStream('books.csv');
csv.write([
[['id'], ['author'], ['title'], ['price'], ['pages'], ['category']],
...books.map(b => [b.author, b.title, b.price, b.pages, b.category])
]).pipe(ws)
After running this script with node index.js
we'll find our book.csv
file created.
$ cat books.csv
# id,author,title,price,pages,category
# J.R.R Tolkien,"The lord of the rings, The Fellowship of the Ring",8.54,555,fiction,
# J.R.R Tolkien,"The lord of the rings, The Two Towers",8.34,467,fiction,
# J.K. Rowling,"Harry Potter and the Philosopher's Stone",8.16,345,fiction,
# Lewis Carroll,Alice in Wonderland,2.7,86,fiction,
# C.S. Lewis,The Chronicles of Narnia,2.99,118,fiction,
Finally, Map can be especially useful when rendering react components, for example, this is how we could render these books in the front end using JSX
.
<div className="books-wrapper">
{
books.map(book => <div key={book.id}>
<div className="book">
<h2>{book.title}</h2>
<p>by {book.author}</p>
<p>{book.pages} pages</p>
<p><strong>$ {book.price}</strong></p>
</div>
</div>
)
}
</div>
Reduce
Reduce is regarded as the most complicated of the three, but we'll get to understand how it works step by step. First, the definition:
Reduce operates over an array and returns a single value. It performs operations over each element and saves the result using an accumulated value. Then it returns that accumulated value.
const callBack = (accumulator, currentValue) => { /* return the next accumulator value */ }
const array.reduce(callback));
Note: Reduce's callback can also receive an index
and the array
as optional parameters.
Let us ask some questions:
- Tell me how much will it cost me to buy all J.R.R Tolkien books in the store.
This is the most basic use of reduce, to add elements in an array.
const tolkiensBooks = books.filter(book => book.author === "J.R.R Tolkien" )
.reduce((first, second) => first.price + second.price);
// => 16.88
- A story:
I have three days off and I want to spend my time reading.
- I like fiction.
- I prefer to buy as many books as I can.
- I have 20 dollars to spend on books.
- I read from 11:00 to 18:00.
- My speed read is 250 wpm (words per minute).
Which books should I buy?
Alright, that's a lot, to eat the elephant you have to start piece by piece, right?
First, this person wants to buy fiction literature, so a simple filter will do books.filter(book => book.category === "fiction" )
. Then we have two restrictions: the budget and the time he has to read. And finally, as he wants to buy as many books as he can that gives us the idea that we have to choose the shipper books first until we get out of money or until we think we won't have the time to finish them in three days.
To tackle this problem we'll use reduce
with a state that holds how much we are spending and how much time we are requiring as we buy book by book. This is the structure:
{
readingTimeLeft: 200,
titles: ["Harry Potter and the Philosopher's Stone", "The Lord of the rings, The Two Towers"],
bill: 19.0
}
As we iterate over the books, we'll be subtracting our reading time left readingTimeLeft
and adding to the bill
that we'll have to pay. In the end, we'll have our list of titles
that we'll buy.
First, we need to perform some calculation, we want to define the variable PAGES_PER_HOUR
. He can read 250
words per minute (the average), that is 15000 [word/hour]
. A book, let's say, have 400 [word/page]
words per page, it can vary, but that will be our estimation. So, he can read a total of 37.5 [page/hour]
pages per hour. (15000 [word/hour]/ 400 [word/page] = 37.5 [page/hour]
). Excellent we have our speeding rate.
If he can read three days from 11hrs to 18hrs, he has a total of 21 [hour]
, so we have our readingTimeLeft
.
Now, we can code.
const PAGES_PER_HOUR = 37.5;
const BUDGET = 20.00;
const initialStructure = {
readingTimeLeft: 21, // hours, 7 hrs, per 3 days.
titles: [],
bill: 0
}
const summary = books.filter(book => book.category === "fiction" )
.sort((first, second) => first.price - second.price)
.reduce((acc, current) => {
const readingTimeLeftAfterCal = acc.readingTimeLeft - (current.pages * (1 / PAGES_PER_HOUR));
const billAfterCal = acc.bill + current.price;
if (readingTimeLeftAfterCal <= 0) return acc; // we run out of time
if (billAfterCal >= BUDGET) return acc; // we run out of budget
return ({
readingTimeLeft: readingTimeLeftAfterCal,
titles: [...acc.titles, current.title], // we add the title
bill: Math.round(billAfterCal * 100) / 100 // we round to two decimals
})
}, initialStructure);
A quick explanation on readingTimeLeftAfterCal
may be needed. We need to subtract the time that will take us reading that current book, to do that we need to subtract hours, that is (current.pages [page] * (1 / PAGES_PER_HOUR [page/hour]))
as our rate is [page/hour]
we need to invert it to have [hour/page]
to cancel the pages and have the hours.
In each iteration we would have this:
{ readingTimeLeft: 21, titles: [], bill: 0 }
{
readingTimeLeft: 18.706666666666667,
titles: [ 'Alice in Wonderland' ],
bill: 2.7
}
{
readingTimeLeft: 15.56,
titles: [ 'Alice in Wonderland', 'The Chronicles of Narnia' ],
bill: 5.69
}
{
readingTimeLeft: 6.359999999999999,
titles: [
'Alice in Wonderland',
'The Chronicles of Narnia',
"Harry Potter and the Philosopher's Stone"
],
bill: 13.85
}
As you can see the last object which is saved in summary
give us all that we need: The titles that we want to buy and how much we need to pay. Of course if you wan you can reverse the readingTimeLeft
left so that you can have how much time you need to finish all those books. I leave that to you ;)
I hope this article was useful to you, If you liked it, if you have a suggestion or if you found a mistake, please leave a comment or send me an email, I'll be grateful.
References
About me
I’m a Software Engineer, writer, tech enthusiast, pianist, origami lover, amateur photographer. In my spare time, I go trekking, play the piano and learn history.
My tech: JavaScript, Node.js, React, Ruby, Crystal, Bash, Docker.
You can follow me on Twitter, LinkedIn or visit my page to contact me.
Top comments (0)