Writing elegant code with map, filter, and reduce in Python
Introduction
One of the first things we learn in programming is how to use for
loops - they are a vital concept to grasp early on. However, there are ways to achieve the same thing, using a lot less code. With Python being a multi-paradigm language, we can look at functional programming, and explore how things are done there. In this article, we will have a look at three important functions from the functional programming paradigm - map
, filter
, and reduce
.
Functions as objects
In functional programming languages, functions are first-class citizens, which means that they are treated the same way as variables. This is also true in Python — you can pass functions as parameters to functions, you can also return functions as a result from functions.
Python also has support for lambda functions — lambdas are small, anonymous functions, which contain a single expression.
Example:
Lambda functions could also be used as arguments for other functions, that accept a function as an argument (e.g. the sort
function)
Example:
On top of that, you can also assign lambdas to a variable, to use later.
Example:
map()
The map
function in Python takes a function and an iterable
(or iterables
) (you can read more about iterables
here), applies that function to each of the elements in the iterable
, and returns a generator object, containing the resulting items.
If we had to write our own map function, it would look like this:
The first argument of the map
is the function to be applied, followed by the iterable
(or iterables
) that the function should be applied on. An example with a predefined function:
An example with a lambda function:
If we pass more than one iterable, our function must accept the correct amount of arguments
The type of items in the list we pass can be different from the type of items in the result. For example, we can do a function that converts an integer in the range [1; 7] to its corresponding day of the week
filter()
filter(), as the name suggests, filters out elements that fall under a certain condition. This condition is in the form of a function, which returns true or false. filter returns all elements of a list, for which the passed function returns True
If we had to write our filter function, it would look something like this:
The first argument of filter is the function that will be used as the criteria for the filtering, followed by the iterable
from which the elements should be filtered out.
As you can see from the example, we want to filter out only the even numbers — which in the example list, are 2 and 4
reduce()
Things become a bit more complex when we introduce the reduce function. Its idea is to take a function and an iterable, apply the function to the first element and an initial value, apply the function again to the result of the first call and second element of the iterable, and so on…
So if we had a function called f, a list [1, 2, 3] and an initial value 0, reduce would do the following: f(f(f(0, 1), 2), 3)
Again, if we had to write our implementation of this function, it would look like this (For simplicity, our reduce will work only for lists of integers):
The first argument of reduce is a function, that accepts two arguments — the accumulated value so far and the next item in the iterable.
Let’s take a look at an example:
We want to sum all the elements of a list and print out the value. Using the reduce, the code would look something like this:
One thing we can do with reduce is to flatten a list (to turn a multidimensional list to a one-dimensional one — e.g. [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
will turn into [1, 2, 3, 4, 5, 6, 7, 8, 9]
)
Examples
Now that we’ve covered the basic usage of map, filter, and reduce, let’s dive into some more complex examples, and compare them to the more “standard” approach of using for-loops.
Example 1
Let’s imagine we have a class called Person, which holds information about a person — first name, last name, email and salary. We have a list of Person instances, with a few entries:
The task is to return a list of all salaries, which are above 2000. Using the standard (object-oriented) way of coding, the code will look something like this:
And by using a simple map and filter commands, we can get the task done in one line:
The first thing we do is take all elements of the people list, do a map over them, in which we return just the salary. Then we pass the result to the filter function, where we have the lambda, which leaves the salaries that are higher than 2000.
If we want, we can expand this, to make it a bit more readable:
Example 2
Let’s say that instead of returning the salaries that are above 2000, we want to see the names of the people who have those salaries:
With the functional style, the code will look something like this:
This does become a bit longer to read — to fix this, we can add a function, that returns just the names of a person.
Then, instead of using a lambda function, we can just pass the newly-created get_names function:
Example 3
Let’s look at another example — we have a variable in a given state (in our case, a certain value), and we need to apply some functions to it, one by one — where the result of the first function will be used as an entry for the second one. For simplicity’s sake, let’s say we have a number of arithmetical operations that we have to apply. Let’s keep them in a list of tuples — the first element will be the operation itself (addition, subtraction, multiplication or division), and the second element will be the second value for the operation.
We will also write a function, which will accept a number, as well as a tuple. It will apply the operation, and return the newly calculated value.
Let’s write a snippet, which applies each of the steps — we will set the initial value to 5.
We can also write this snippet, using the reduce function. The code will look something like this:
Quick reminder, that the function that we pass to reduce should accept two arguments from which the first should be the value accumulated so far. The second value is the current element that will be applied.
Example 4
The final example is from one of my personal projects — a CLI tool for editing images. We will take a look at one specific snippet of code.
This code opened an image and applied filters to the image. It took the first filter and applied it to the initial image. The resulting image was used as input for the second filter, and so on. The way I refactored this code was the following — first I separated the code that applied a given filter to a given image in a separate function.
And in the main code I added a reduce call, with the newly created function:
Readability
One thing to consider is the readability of the code written — for some people, this way of writing code may be a bit confusing. If we take a look at Example 4, if a person is not familiar with the concept of reduce, the code will look a bit complex. However, if you know functional programming and how it differs from object-oriented programming, it can become a powerful tool in your arsenal — you can write elegant code, which can also be easy to read. As with most things, the key is balance.
Final thoughts
In this article, we went through the map, filter, and reduce functions — what they do and how to use them. They take a function and an iterable and produce a result — either a new iterable or a single value. As always, I’d love to hear your feedback in the comments below.