Map, filter and reduce functions are the pillars of functional programming. While Python isn’t a purely functional programming language, it boasts many features that allow it to be used as such. This article covers the usage of Python’s map, filter and reduce functions and how they conform to the ideas of functional programming.
In imperative programming — the more common programming paradigm you’re probably already familiar with, computation is carried out through statements. These consist of commands whose execution changes a variable’s value, and therefore the state of the computation. For example, a for loop may repeatedly execute a statement, each time changing the value of a variable, as below:
As the value of counter increases each iteration of the loop by 1, the computation’s state experiences a corresponding change, each time advancing closer towards the final state.
In contrast, functional programming eliminates the notion of state. Instead of changing the values of variables, functional programming works only on immutable data types. Because the values of immutable data types cannot be altered, we only work on the copies of the original data structure. Map, filter and reduce functions in Python work precisely in this manner — they create and work on new sets of data, leaving the original data intact.
Functions are first-class citizens in functional programming. Whereas in imperative programming in Python we normally transform data using for loops and list comprehensions, in functional programming we may only use functions to transform data. This results in elegant solutions and benefits such as increased modularity and efficiency in parallel program execution.
Before we dive into examples of map(), filter() and reduce() functions in Python, there’s another concept we’ll have to cover: higher-order functions.
Higher-order functions are our core tool for defining computation in functional programming. These are functions that may accept a function as an argument or return a function as its output. In Python, reduce(), map() and filter() are some of the most important higher-order functions. When combined with simpler functions, they can be used to execute complex operations.
The code snippet below illustrates an example of a higher-order function. print_greeting() accepts a function f and a name n as its arguments and returns the result of calling f(n).
In the previous section we showed an example of a function that accepts another function as an argument. map(), filter() and reduce() work in the same way: They each accept a function and a sequence of elements and return the result of applying the received function to each element in the sequence. In the two examples above, we’d defined our functions using Python’s def keyword. This creates a callable Python object that executes the statement specified within its definition every time we call it.
However, in functional programming, anonymous functions (also called lambda functions) are preferred. They’re called anonymous because they’re not bound to any name, in line with functional programming’s tendency towards statelessness. The difference between lambda functions and regular Python functions is that lambda functions evaluate a single expression and return a function, whereas the latter do not necessarily have to do it. This means that lambda functions cannot use statements such as conditions or even the return keyword. Here’s an example of a lambda function:
We could also define this operation using the def keyword and receive the same output:
In the first code snippet, we used lambda to define the function inline and called it with the argument n=5. It was immediately evaluated to produce an output. Unlike in the second example, the lambda function wasn’t tied to a keyword, so if we wanted to use it again, we’d have to reproduce the same code.
Lambdas are important for map(), filter() and reduce() because the arguments that we pass to them are often short functions needing to be used only once in our programs, so there’s no use in saving them. Unlike regular functions that need to be defined and saved in memory, anonymous functions are succinct and disposable.
Now that we’ve gone over the preliminaries, let’s take a deeper look into map(), filter() and reduce() Python functions.
Python’s map() function has the following syntax:
map() applies the function it receives as an argument to each element in a sequence and returns the resulting sequence. In the example below, we create a list of integers and using map() and Python’s str() function, we cast each integer into a string. The output is a list of strings.
Notice how we enclose the result returned by the map() function within a list. We do this because in Python, map() returns the memory address of the returned map generator object.
Python generators employ a strategy known as lazy evaluation, which means that Python doesn’t evaluate objects until the moment we need to use them. Although it might sound counterintuitive, evaluating an object right away is not always the best solution since not every expression is worth evaluating. Lazy evaluation increases efficiency by reducing unnecessary computation. This is why we enclosed a map generator within a list – doing so evaluates the generator expression and saves the result into an easily accessible list object.
Another way to retrieve results from a generator is to iterate through it with a for loop:
Python’s map() function can also be implemented with list comprehensions. Unlike generators, Python evaluates list comprehensions right away. Here’s an example.
Python’s filter() function has the following syntax:
filter() takes a function called a predicate that returns “True” if an element passes the condition stated in the predicate, and “False” otherwise. The filter() function’s output consists of the elements of the original sequence that pass the predicate.
In the code below, we use the filter() function on a sequence of countries to filter out the countries with names that don’t contain the string “and.”
The countries for which the lambda function returns “True” are Bosnia and Herzegovina, Finland and Ireland, and these are the countries that the filter() function keeps; the rest of the sequence is rejected.
Alternatively, we can filter elements of a sequence with list comprehensions. This is considered to be a more Pythonic solution.
Note that we may chain map(), filter() and reduce() functions together in the same call. Let’s say we arbitrarily want to filter out the countries whose names don’t contain “land” and then convert the remaining countries’ names into uppercase. We could do this by combining Python’s filter() and map() functions like so:
Python’s reduce() function doesn’t return a new sequence like map() and filter(). Instead, it returns a single value. The syntax is similar to the other two functions:
reduce() applies the function to the elements of the sequence, from left to right, starting with the first two elements in the sequence. We combine the result of applying the function to the sequence’s first two elements with the third element and pass them to another call of the same function. This process repeats until we reach the end of the iterable and the iterable reduces to a single value.
Let’s take a look at an example where reduce() takes as the argument a lambda function that multiplies two numbers, in consecutive fashion:
945 is the result of multiplying all elements of the numbers sequence.
The two expressions below are equivalent:
Note that in order to use reduce in Python 3.x, you have to import it using the following import statement:
In this article, we worked with Python’s map, filter and reduce functions. In continuing your Python journey, check out our Introduction to Programming Nanodegree to make your first step towards careers in Web and App Development, Machine Learning, Data Science or AI.