map(), filter(), and reduce() in Python with Examples

Introduction

The map(), filter() and reduce() functions bring a bit of functional programming to Python. All three of these are convenience functions that can be replaced with List Comprehensions or loops, but provide a more elegant and short-hand approach to some problems.

Before continuing, we'll go over a few things you should be familiar with before reading about the aforementioned methods:

What is an anonymous function/method or lambda?

An anonymous method is a method without a name, i.e. not bound to an identifier like when we define a method using def method:.

Note: Though most people use the terms "anonymous function" and "lambda function" interchangeably - they're not the same. This mistake happens because in most programming languages lambdas are anonymous and all anonymous functions are lambdas. This is also the case in Python. Thus, we won't go into this distinction further in this article.

What is the syntax of a lambda function (or lambda operator)?

lambda arguments: expression

Think of lambdas as one-line methods without a name. They work practically the same as any other method in Python, for example:

def add(x,y):
	return x + y

Can be translated to:

lambda x, y: x + y

Lambdas differ from normal Python methods because they can have only one expression, can't contain any statements and their return type is a function object. So the line of code above doesn't exactly return the value x + y but the function that calculates x + y.

Why are lambdas relevant to map(), filter() and reduce()?

All three of these methods expect a function object as the first argument. This function object can be a pre-defined method with a name (like def add(x,y)).

Though, more often than not, functions passed to map(), filter(), and reduce() are the ones you'd use only once, so there's often no point in defining a referenceable function.

To avoid defining a new function for your different map()/filter()/reduce() needs - a more elegant solution would be to use a short, disposable, anonymous function that you will only use once and never again - a lambda.

The map() Function

The map() function iterates through all items in the given iterable and executes the function we passed as an argument on each of them.

The syntax is:

map(function, iterable(s))

We can pass as many iterable objects as we want after passing the function we want to use:

# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(starts_with_A, fruit)

print(list(map_object))

This code will result in:

[True, False, False, True, False]

As we can see, we ended up with a new list where the function starts_with_A() was evaluated for each of the elements in the list fruit. The results of this function were added to the list sequentially.

A prettier way to do this exact same thing is by using lambdas:

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(lambda s: s[0] == "A", fruit)

print(list(map_object))

We get the same output:

[True, False, False, True, False]

Note: You may have noticed that we've cast map_object to a list to print each element's value. We did this because calling print() on a list will print the actual values of the elements. Calling print() on map_object would print the memory addresses of the values instead.

The map() function returns the map_object type, which is an iterable and we could have printed the results like this as well:

for value in map_object:
    print(value)

If you'd like the map() function to return a list instead, you can just cast it when calling the function:

result_list = list(map(lambda s: s[0] == "A", fruit))

The filter() Function

Similar to map(), filter() takes a function object and an iterable and creates a new list.

As the name suggests, filter() forms a new list that contains only elements that satisfy a certain condition, i.e. the function we passed returns True.

The syntax is:

filter(function, iterable(s))

Using the previous example, we can see that the new list will only contain elements for which the starts_with_A() function returns True:

# Without using lambdas
def starts_with_A(s):
    return s[0] == "A"

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(starts_with_A, fruit)

print(list(filter_object))

Running this code will result in a shorter list:

['Apple', 'Apricot']

Or, rewritten using a lambda:

fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(lambda s: s[0] == "A", fruit)

print(list(filter_object))

Printing gives us the same output:

['Apple', 'Apricot']

The reduce() Function

reduce() works differently than map() and filter(). It does not return a new list based on the function and iterable we've passed. Instead, it returns a single value.

Also, in Python 3 reduce() isn't a built-in function anymore, and it can be found in the functools module.

The syntax is:

reduce(function, sequence[, initial])

reduce() works by calling the function we passed for the first two items in the sequence. The result returned by the function is used in another call to function alongside with the next (third in this case), element.

This process repeats until we've gone through all the elements in the sequence.

The optional argument initial is used, when present, at the beginning of this "loop" with the first element in the first call to function. In a way, the initial element is the 0th element, before the first one, when provided.

reduce() is a bit harder to understand than map() and filter(), so let's look at a step by step example:

  1. We start with a list [2, 4, 7, 3] and pass the add(x, y) function to reduce() alongside this list, without an initial value

  2. reduce() calls add(2, 4), and add() returns 6

  3. reduce() calls add(6, 7) (result of the previous call to add() and the next element in the list as parameters), and add() returns 13

  4. reduce() calls add(13, 3), and add() returns 16

  5. Since no more elements are left in the sequence, reduce() returns 16

The only difference, if we had given an initial value would have been an additional step - 1.5. where reduce() would call add(initial, 2) and use that return value in step 2.

Let's go ahead and use the reduce() function:

from functools import reduce

def add(x, y):
    return x + y

list = [2, 4, 7, 3]
print(reduce(add, list))

Running this code would yield:

16

Again, this could be written using lambdas:

from functools import reduce

list = [2, 4, 7, 3]
print(reduce(lambda x, y: x + y, list))
print("With an initial value: " + str(reduce(lambda x, y: x + y, list, 10)))

And the code would result in:

16
With an initial value: 26

Conclusion

As mentioned previously, these functions are convenience functions. They are there so you can avoid writing more cumbersome code, but avoid using both them and lambda expressions too much.

Don't force these tools because "you can", as it can often lead to illegible code that's hard to maintain. Use them only when it's absolutely clear what's going on as soon as you look at the function or lambda expression.

If you catch yourself struggling to fit the necessary logic into one map() function, or one lambda expression, it's much better to just write a slightly longer for-loop/defined method and avoid unnecessary confusion later.

Author image
Hey guys, I want to point out that I don't have any social media to avoid mistakes. If you need any help - post it in the comments :)