ELI5 - "Map-filter-reduce"

__namc

Namc

Posted on December 4, 2017

ELI5 - "Map-filter-reduce"

Many developers that I have lately interacted with associate map-reduce-filter with functional programming, and they are actually dreaded by the combination. It’s one of the reasons they avoid using functional languages, because god forbid they would have to learn how to use map, reduce and filter — 3 functions present in Ruby, Python, Javascript, PHP, Erlang, Scala and many others languages.

So what’s the big deal with map-reduce-filter anyway?

They are just higher-order functions used in functional programming. And they work on anything that is iterable (list, tuple, sets, dicts, and even strings).

Let’s consider a problem statement to be able to follow more clearly. (We’re using Python for the ease of purpose, but I’ll add a gist of how the same thing can be done in various other languages)

Problem statement : 
We need the sum of all even squares of integers in the range [0, 9]

Map (Transform)

This function takes a unary function and a list. Unary function is a function that takes one variable. The resultant for map function is a same-sized list of transformed values. The transformation of value is subject to the definition of unary function with the parameter. 

Use when : You want to transform all elements in a set to another set of values.

Parameters : unary transformation function & array to be transformed

In order to solve the problem statement, let’s say, our first step is to map the function of calculating squares over the range of numbers [0, 9]

>>> nums = map(lambda x : x*x, [i for i in range(10)])
>>> nums
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Enter fullscreen mode Exit fullscreen mode

Lambdas are often to define map functions. You can use regular functions as well.

Filter 

This function takes a predicate, (a unary function which returns a bool) and the set of values; and gives a resultant set with only those set of values for which the predicate returns true. The resultant set may or may not be of the same length. 

Use when : You want to remove elements from a set based on a condition. 

Parameters : unary predicate (should only return a bool) & array to be filtered. 

In context to our problem above, let’s try to remove all the odd squares from the list. For that, let our predicate return true if the element is even.

 

>>> def is_even(x):
        return x % 2 == 0
>>> evens = filter(is_even, nums)
>>> evens
[0, 4, 16, 36, 64]
Enter fullscreen mode Exit fullscreen mode

Comprehensions often make it easier to write filters. Those can be used in place of functions as well.

Reduce (Accumulate)

This function takes a binary function and a set of values, and it produces a single value. It does so by running the binary function over the set of values, and accumulating them into a single value. 

Use when : You want an accumulated or concatenated value using given set of values.

Parameters : binary function and set of values.

In context to our problem above, we shall add up all the even-valued squares.

>>> reduce(lambda x,y : x+y, evens)
120
Enter fullscreen mode Exit fullscreen mode

If we look at the defintion of reduce in functools module,

def reduce(f,alist)
    if alist == []:
        return None

    answer = alist[0]
    for v in alist[1::
        answer = f(answer,v)
    return v
Enter fullscreen mode Exit fullscreen mode

we see that reduce function starts by setting answer as the first element of array. Then it traverses the array from left to right, invoking a callback function on each element. The cumulative value is passed from callback to callback. After all elements are traversed, cumulative value is returned. 

Combining map-filter-reduce

We saw a lot of small functions being written above, that was just for understanding. The true power of higher-order-functions lie in combination. So our problem statement can be solved in one line of code 😅

>>> reduce(lambda a, b: a + b, map(lambda x: x * x, filter(lambda y: not y % 2, range(10))))
120
Enter fullscreen mode Exit fullscreen mode

Don’t Panic! ! We can break it down for simplicity.

 

f_add = lambda a, b: a + b
f_square = lambda x: x * x
f_is_even = lambda y: not y % 2
Enter fullscreen mode Exit fullscreen mode

Now the complex expression can be rewritten as

 

>>> reduce(f_add , map(f_square , filter(f_is_even, range[0, 10]))
Enter fullscreen mode Exit fullscreen mode

As a sidenote, there is no restriction on using map , filter & reduce together. You can make your own combinations, and order your functions as per the need.

Other languages 

Keeping the problem statement same as above, check out how map-reduce-filter can be used in other programming languages, and is not a rocket science at all 🚀

Ruby

def main()

    nums = [*0..9]

    ans = nums.map{|x| x * x}              # map operation (x*x) over nums
              .select{|x| x % 2 == 0}      # select event elements
              .reduce{|a, x| a + x}        # reduce the list by `+` operation

    p nums                                 
    p ans                                  

end
main()
Enter fullscreen mode Exit fullscreen mode

Clojure

(reduce (fn [a b] (+ a b)) 
        (map (fn [x] (* x x)) 
             (filter even? (range 10))))
Enter fullscreen mode Exit fullscreen mode

Javascript

(I’m really bad at this 😕)

let numbers = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

ans = numbers 
    .filter(function(num) {
        return (num%2 === 0);
    })
    .map(function(num) { 
        return num*num;
    })
    .reduce(function(total, num){
        return total += num;
    });

Enter fullscreen mode Exit fullscreen mode

Try it out in other languages as well!

💖 💪 🙅 🚩
__namc
Namc

Posted on December 4, 2017

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ELI5 - "Map-filter-reduce"
explainlikeimfive ELI5 - "Map-filter-reduce"

December 4, 2017