BishopPhillips
BishopPhillips
BPC Home BPC Python Topic Home BPC RiskManager BPC SurveyManager BPC RiskWiki Learn HTML 5 and CSS Enquiry

Python Techniques - Maps and Filters

Using Maps and Filters.

Author: Jonathan Bishop
AI Revolution

Python’s functional programming capabilities provide powerful tools for efficiently manipulating data. Among these, map() and filter() are two fundamental functions that enable elegant, concise, and highly readable transformations on iterables. The functions map() and filter() are two Python routines that operate on lists by applying a function to each element of the list. By combining them with other functions such as zip() and reduce() as well as lambda functions, list comprehension and the multiprocessing module we can create some powerful workflows in Python. These functions allow developers to apply operations efficiently to collections of data without explicit looping, significantly improving code clarity and performance.

The map() function applies a given operation to each element in an iterable, making it useful for transformations such as mathematical computations, text formatting, or data normalization. Conversely, the filter() function selectively removes elements based on specified conditions, enabling efficient data cleansing and filtering operations.

One of the interesting characteristics of Python is that it embraces both functional and object programming paradyms simultaneously. In particular it captures a significant aspect of a lisp-like functional programming feel with its extensive list processing capabilities and functional model. In my view it is considerably weaker as an object based programming language, when compared to other environments such as Delphi but does an excellent job of marrying functional and object programming concepts, so that the aspects of its object programming capabilities that I judge to be weaknesses probably enable it to be considerably stronger in the functional space, so it is probably the right set of compromises.

Functional programming has deep historical roots, emerging from lambda calculus, a formal system developed by Alonzo Church in the 1930s. Lambda calculus introduced the concept of treating functions as first-class entities—allowing functions to be passed as arguments, returned from other functions, and used as values. This laid the foundation for modern functional programming languages.

In other papers of this series we have explored other aspects of Python's functional model, such as closures, list comprehensions, generators, etc. Here we explore the mechanics, practical applications, and performance considerations of map() and filter(), demonstrating their strengths and comparing them with alternative approaches. By understanding their capabilities and limitations, developers can harness their full potential to write efficient and maintainable Python code.  It is worth noting, however that much of what we can do with map() and filter() can be achieved with list comprehensions as is demonstrated in the examples below where we are able to rewrite many of the examples with list comprehensions.



1. The Map and Filter Functions

The Map() Function

The map() function in Python applies a given function to each element in an iterable and returns a map object, which can be converted to a list, tuple, or other collection. Note here - the map function does NOT return a list, but rather a map object which is a kind of generator, so to get it to be a list you will have to wrap the return result in the list() constructor.

Basic Syntax

map(function, iterable)
  • function → A function that transforms each element.

  • iterable → A list, tuple, or any sequence to be processed.

  • Returns a map object that can be converted to a list or other sequence.

Simple Example

numbers = [1, 2, 3, 4, 5]

# Multiply each number by 2
doubled = list(map(lambda x: x * 2, numbers))

print(doubled)  # Output: [2, 4, 6, 8, 10]

  • Uses lambda for concise function definition.
  • Applies the function to all elements in the list.
  • Returns a transformed list.

Using map() with Multiple Iterables


a = [1, 2, 3]
b = [4, 5, 6]

# Add corresponding elements from two lists
added = list(map(lambda x, y: x + y, a, b))

print(added)  # Output: [5, 7, 9]

  • Processes multiple iterables in parallel!

 

The Filter() Function

The filter() function in Python selectively removes elements from an iterable based on a given condition. It takes two arguments: A function that determines which elements to keep and an iterable to be filtered.

Basic Syntax

filter(function, iterable)

  • function: Returns True for elements to keep and False for elements to discard.

  • iterable: A list, tuple, or other sequence to be processed.

  • Returns a filter object that can be converted to a list, tuple, or set.

Simple Example


numbers = [1, 2, 3, 4, 5, 6]

# Keep only even numbers
evens = list(filter(lambda x: x % 2 == 0, numbers))

print(evens)  # Output: [2, 4, 6]

  • Applies a filtering condition using a lambda function.
  • Removes non-even numbers from the list.
  • Returns only the elements that meet the condition.

Using filter() with Named Functions


def is_positive(n):
    return n > 0

numbers = [-3, -2, -1, 0, 1, 2, 3]
positives = list(filter(is_positive, numbers))

print(positives)  # Output: [1, 2, 3]

  • Uses a named function for better readability.
  • Filters out non-positive numbers.

 



2. Using map() for Complex Transformations

The map() function applies a given function to all elements in an iterable.

# Apply multiple functions dynamically using map
functions = [lambda x: x**2, lambda x: x**3, lambda x: x**4]

values = [2, 3, 4]

# Apply each function to each value
result = [list(map(f, values)) for f in functions]

print(result)

Output:

[[4, 9, 16], [8, 27, 64], [16, 81, 256]]

  • Applies multiple transformations efficiently without explicit loops.
  • Maintains readability while dynamically applying operations.


List Comprehension Rewrite

You can rewrite this using list comprehension instead of map() to achieve the same result:

functions = [lambda x: x**2, lambda x: x**3, lambda x: x**4]
values = [2, 3, 4]

# Apply each function to each value using list comprehension
result = [[f(x) for x in values] for f in functions]

print(result)



3. Using filter() for Conditional Filtering

The filter() function removes items that don’t meet a condition.

# Filter numbers that are prime
def is_prime(n):
    if n < 2:
        return False
    return all(n % i != 0 for i in range(2, int(n**0.5) + 1))

numbers = range(10, 30)
prime_numbers = list(filter(is_prime, numbers))

print(prime_numbers)

Output: [11, 13, 17, 19, 23, 29]

  • Filters values efficiently using filter() instead of manual loops.
  • Improves readability compared to for loops with if conditions.


List Comprehension Rewrite

You can rewrite this using list comprehension, which is more concise and Pythonic:

numbers = range(10, 30)

# Using list comprehension instead of filter()
prime_numbers = [n for n in numbers if all(n % i != 0 for i in range(2, int(n**0.5) + 1))]

print(prime_numbers)



 

4. Combining map() and filter()

You can combine both to transform and filter data in a single pass.

numbers = range(1, 21)

# Square only even numbers
squared_evens = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, numbers)))

print(squared_evens)

Output: [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

  • Efficiently filters even numbers first,  the result of which is provided as the iterable argument of map which then squares them using map().


List Comprehension Rewrite

You can achieve the same result using list comprehension, which is often more readable and Pythonic:

numbers = range(1, 21)

# Square only even numbers using list comprehension
squared_evens = [x**2 for x in numbers if x % 2 == 0]

print(squared_evens)



 

5. Advanced: Using map() and filter() with zip()

Combining zip() allows processing multiple lists in parallel.

names = ["Alice", "Bob", "Charlie", "David"]
scores = [85, 92, 78, 90]

# Filter students who scored above 80 and capitalize their names
passed_students = list(map(lambda x: x[0].upper(), filter(lambda x: x[1] >= 80, zip(names, scores))))

print(passed_students)

Output: ['ALICE', 'BOB', 'DAVID']

  • Combines filtering and transformation elegantly.


List Comprehension Rewrite

You can rewrite this using list comprehension, which is often more readable and concise:

names = ["Alice", "Bob", "Charlie", "David"]
scores = [85, 92, 78, 90]

# Filter students who scored above 80 and capitalize their names using list comprehension
passed_students = [name.upper() for name, score in zip(names, scores) if score >= 80]

print(passed_students)



 

6. Using reduce() for Functional Composition

Functional composition using reduce(), which allows us to perform cumulative operations on iterables efficiently.

from functools import reduce

# List of transformation functions
transformations = [
    lambda x: x * 2,   # Double the number
    lambda x: x + 3,   # Add 3
    lambda x: x ** 2   # Square the result
]

# Apply transformations sequentially using reduce
def apply_transformations(x):
    return reduce(lambda val, func: func(val), transformations, x)

# Apply to multiple numbers
numbers = [1, 2, 3, 4, 5]
processed_numbers = list(map(apply_transformations, numbers))

print(processed_numbers)

Output for [1, 2, 3, 4, 5]:

[25, 49, 81, 121, 169]

How It Works

  • Uses reduce() to apply multiple functions in sequence
  • Transforms each number dynamically with map()
  • Eliminates manual looping for cleaner code
  • Each number follows this transformation
    • 1 → (1 * 2) → (2 + 3) → (5 ** 2) → 16:


List Comprehension Rewrite

You can achieve the same result using list comprehension, but since reduce() applies multiple transformations in sequence, it requires nesting reduce() within the list comprehension:

from functools import reduce

# List of transformation functions
transformations = [
    lambda x: x * 2,   # Double the number
    lambda x: x + 3,   # Add 3
    lambda x: x ** 2   # Square the result
]

# Using list comprehension instead of map()
numbers = [1, 2, 3, 4, 5]
processed_numbers = [reduce(lambda val, func: func(val), transformations, x) for x in numbers]

print(processed_numbers)


In this rewrite we are still using "reduce()" which applies the functions sequentially.  Now while list comprehension should be slightly faster for simple transformations than map(), using reduce inside it introduces a function call overhead which probably biases the advantage back to map() as map() is implemented directly in c and has the advantage of c's very efficient function call architecture.  

If efficiency is your primary goal, consider avoiding reduce() altogether:

numbers = [1, 2, 3, 4, 5]

# Inline transformation using list comprehension
processed_numbers = [(x * 2 + 3) ** 2 for x in numbers]

print(processed_numbers)


Now the point here was not really about the most efficient code, but really the algorithmic design advantage potentially offered by being able to stack a list of transformation functions, potentially undetermined at the coding stage but dynamically determined by the program as it runs, and then being able to apply them.  So the use-case for the reduce() approach is really about dynamic flexibility of the function list formation and application during execution rather than the fact that if we know in advance exactly what transformations are going to be required we can recode them as a list comprehension and avoid the reduce() overhead entirely.

At the end of this paper we consider the execution times of these three approaches by way of contrasting and comparing map, hybrid comprehension and pure comprehension approaches.



7. Parallel Computation with map()

Python’s map() can be combined with multiprocessing for parallel execution.

from multiprocessing import Pool

def square(x):
    return x ** 2

numbers = range(1, 6)

with Pool(processes=4) as pool:
    results = pool.map(square, numbers)

print(results)

Output: [1, 4, 9, 16, 25]

How It Works

  • Uses multiprocessing for parallel execution
  • Optimizes performance for large datasets


List Comprehension Rewrite

This example cannot be rewritten using list comprehension AND preserve the multiprocessing capability.  It obviously can be rewritten without the multiprocessing capability as a list comprehension, but this defeats the entire point of the example so I won't add it here.



Comparing map() and List Comprehension Execution Efficiency.

Let's compare the execution times of three approaches:

  1. Using map() with reduce()
  2. Using list comprehension with reduce()
  3. Optimized list comprehension without reduce()



Benchmarking Execution Time

from functools import reduce
import time

# List of transformation functions
transformations = [
    lambda x: x * 2,   # Double the number
    lambda x: x + 3,   # Add 3
    lambda x: x ** 2   # Square the result
]

# Numbers to process
numbers = list(range(1, 10000))

# Approach 1: Using map() with reduce()
start = time.time()
processed_map_reduce = list(map(lambda x: reduce(lambda val, func: func(val), transformations, x), numbers))
end = time.time()
print(f"Map + Reduce Execution Time: {end - start:.6f} seconds")

# Approach 2: Using list comprehension with reduce()
start = time.time()
processed_list_reduce = [reduce(lambda val, func: func(val), transformations, x) for x in numbers]
end = time.time()
print(f"List Comprehension + Reduce Execution Time: {end - start:.6f} seconds")

# Approach 3: Optimized list comprehension without reduce()
start = time.time()
processed_list_direct = [(x * 2 + 3) ** 2 for x in numbers]  # Direct transformation without reduce()
end = time.time()
print(f"Optimized List Comprehension Execution Time: {end - start:.6f} seconds")

Results


Map + Reduce Execution Time: 0.008997 seconds
List Comprehension + Reduce Execution Time: 0.014001 seconds
Optimized List Comprehension Execution Time: 0.000999 seconds

  • The optimized list comprehension (Approach 3) is the fastest because it eliminates unnecessary function calls and avoids the overhead of reduce() and because list comprehensions are optimised for high performance in python.
  • List comprehension + reduce() (Approach 2) is slightly slower  than map() + reduce() (Approach 1) due to Python's optimizations for list comprehensions being impacted by the reduce() function calls.
  • map() + reduce() (Approach 1) is the second fastest because while map() and reduce() each introduce additional function call overhead, the implementation of map() in c gives it a speed advantage with function execution (my assumed reason!), but it does not achieve the performance of a direct native python list comprehension.



Summary

Map() and filter() are excellent for processing iterables in a way that both preserves readability and, when combined with other capabilities, considerably expands the scope of list applicability, however as demonstrated in the examples, much of what map and filter can do is able to be successfully rewritten using list comprehension, so consideration should be paid to whether your proposed code is better done in this latter form than the former.  There is a clear advantage in parallel processing to using map and filter, however, as that is not something that I can see how to apply list comprehension over at this point.