Functional Programming: Filter, Map and Reduce in JS (Advance).
Tags: javascript, filter, map, reduce
Published: 2020-09-17 22:49:11

# 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.

<div class="success">
In this article you will learn:
	<ul>
		<li>A better understanding of these functions.</li>
		<li>Check on complex cases to see their potential.</li>
	</ul>
</div>

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`.

```javascript
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:

```javascript
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:

1. Give me all books from J.R.R Tolkien.
2. Give me all books that worth less than 5 dollars.
3. Give me all books with less than 100 pages.

Give me all books from J.R.R Tolkien.

```javascript
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.

```javascript
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.

```javascript
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:

```javascript
const callback = (currentValue, index, array) => { /* mapping */}
const newArray = array.map(callback)
```

`index` and `array` are optional.

We'll have three examples.

1. Give me all the titles, that worth less than 5 dollars.
2. Export the data to a CSV file.
3. 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.

```javascript
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.

```javascript
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.

```bash
$ 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`.

```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.

```javascript
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:

1. 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.

```javascript
const tolkiensBooks = books.filter(book => book.author === "J.R.R Tolkien" )
  .reduce((first, second) => first.price + second.price);
// => 16.88
```

2. 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:

```javascript
{
  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.

```javascript
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:

```javascript
{ 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

- [Filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
- [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- [Reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)
- [Sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
- Originally posted in my [page](https://sespinoza.me/#/articles/5f6387ce9a9e50fef54d09a9)

## 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](https://twitter.com/sespinoza_dev), [LinkedIn](https://www.linkedin.com/in/espinoza-jimenez/) or visit my page to contact [me](https://sespinoza.me/#/contact/).