11.3. FP Apply Reduce

  • Reduce sequence using function

  • Built-in

In Python, reduce() is a built-in function that applies a given function to the elements of an iterable (e.g. a list, tuple, or set) and returns a single value. The function takes two arguments: the first is the function to apply, and the second is the iterable to be processed.

The reduce() function works by applying the function to the first two elements of the iterable, then to the result and the next element, and so on, until all elements have been processed and a single value is obtained.

Here's an example:

>>> from functools import reduce
>>>
>>> # Define a list of numbers
>>> numbers = [1, 2, 3, 4, 5]
>>>
>>> # Use reduce() to sum the numbers
>>> result = reduce(lambda x,y: x+y, numbers)
>>>
>>> print(result)
15

In this example, the reduce() function applies the lambda function (which adds two numbers) to the elements of the numbers list, resulting in the sum of all the numbers.

11.3.1. SetUp

>>> from functools import reduce

11.3.2. Syntax

11.3.3. Problem

>>> def add(x, y):
...     return x + y
>>>
>>>
>>> DATA = [1, 2, 3, 4]
>>> result = 0
>>>
>>> for element in DATA:
...     result = add(result, element)
>>>
>>> print(result)
10

11.3.4. Solution

>>> DATA = [1, 2, 3, 4]
>>>
>>>
>>> def add(x, y):
...     return x + y
>>>
>>> reduce(add, DATA)
10

11.3.5. Rationale

The operator module in Python provides a set of functions that implement common operations on Python objects, such as arithmetic operations, comparisons, and logical operations. These functions are designed to be used as functional arguments to other functions, such as map(), filter(), and reduce().

The operator module provides functions for arithmetic operations such as addition, subtraction, multiplication, division, and exponentiation. It also provides functions for bitwise operations, such as bitwise AND, OR, XOR, and shift operations.

In addition to arithmetic and bitwise operations, the operator module provides functions for comparisons, such as greater than, less than, equal to, and not equal to. It also provides functions for logical operations, such as not, and, and or.

Here's an example of using the operator module to sort a list of tuples based on the second element of each tuple:

>>> import operator
>>>
>>> # Define a list of tuples
>>> data = [(2, 'b'), (1, 'a'), (3, 'c')]
>>>
>>> # Sort the list based on the second element of each tuple
>>> sorted_data = sorted(data, key=operator.itemgetter(1))
>>>
>>> print(sorted_data)
[(1, 'a'), (2, 'b'), (3, 'c')]

In this example, the itemgetter() function from the operator module is used as the key argument to the sorted() function. This function returns a callable that extracts the second element of each tuple, which is used to sort the list.

>>> DATA = [1, 2, 3, 4]
>>>
>>> from operator import mul
>>> reduce(mul, DATA)
24
>>> def add(a, b):
...     print(f'{a=}, {b=}')
...     return a + b
>>>
>>>
>>> data = [1, 2, 3, 4]
>>> reduce(add, data)
a=1, b=2
a=3, b=3
a=6, b=4
10

11.3.6. Use Case - 1

>>> from functools import reduce
>>>
>>> data = (1, 2, 3, 4)
>>> def add(x, y):
...     return x + y
>>>
>>> def sub(x, y):
...     return x - y
>>>
>>> def mul(x, y):
...     return x * y
>>>
>>>
>>> reduce(add, data)
10
>>> reduce(sub, data)
-8
>>> reduce(mul, data)
24
>>> reduce(lambda x,y: x+y, data)
10
>>> reduce(lambda x,y: x-y, data)
-8
>>> reduce(lambda x,y: x*y, data)
24
>>> from operator import add, sub, mul
>>>
>>> reduce(add, data)
10
>>> reduce(sub, data)
-8
>>> reduce(mul, data)
24

11.3.7. Use Case - 2

>>> from functools import reduce
>>> from operator import add
>>> data = [
...     [1, 2, 3],
...     [4, 5, 6],
...     [7, 8, 9],
... ]
>>> result = 0
>>>
>>> for row in data:
...     for digit in row:
...         result += digit
>>>
>>> print(result)
45
>>> sum(data[0])
6
>>>
>>> sum(data[1])
15
>>>
>>> sum(data[2])
24
>>>
>>>
>>> sum(data[0]) + sum(data[1]) + sum(data[2])
45
>>> reduce(add, data[0])
6
>>>
>>> reduce(add, data[1])
15
>>>
>>> reduce(add, data[2])
24
>>>
>>>
>>> reduce(add, (
...     reduce(add, data[0]),
...     reduce(add, data[1]),
...     reduce(add, data[2]),
... ))
45

11.3.8. Use Case - 3

>>> from functools import reduce
>>> from itertools import starmap
>>> from operator import add, sub, mul
>>> def square(x):
...     return x ** 2
>>>
>>> def cube(x):
...     return x ** 3
>>>
>>> def apply(data, fn):
...     return map(fn, data)
>>> data = (1, 2, 3, 4)
>>> funcs = (square, cube)
>>>
>>> result = reduce(apply, funcs, data)
>>> tuple(result)
(1, 64, 729, 4096)
>>>
>>> result = reduce(apply, funcs, data)
>>> reduce(add, result)
4890
>>> data = (1, 2, 3, 4)
>>> funcs = (add, sub, mul)
>>>
>>> result = map(lambda fn: reduce(fn,data), funcs)
>>> reduce(add, result)
26
>>> funcs = (
...     (add, data),
...     (sub, data),
...     (mul, data),
... )
>>>
>>> data = (1, 2, 3, 4)
>>> result = starmap(reduce, funcs)
>>> reduce(add, result)
26
>>> result = starmap(reduce, (
...     (add, data),
...     (sub, data),
...     (mul, data),
... ))
>>>
>>> data = (1, 2, 3, 4)
>>> reduce(add, result)
26

11.3.9. Use Case - 4

  • split-apply-combine strategy

Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.

Roughly equivalent to:

>>> def reduce(function, iterable, initializer=None):
...     it = iter(iterable)
...     if initializer is None:
...         value = next(it)
...     else:
...         value = initializer
...     for element in it:
...         value = function(value, element)
...     return value

SetUp:

>>> from functools import reduce
>>>
>>> DATA = (1, 2, 3, 4, 5)

Usage:

>>> def add(a, b):
...     return a + b
>>>
>>> reduce(add, DATA)
15
>>> reduce(lambda x,y: x+y, DATA)
15

11.3.10. Use Case - 5

>>> def square(x):
...     return x ** 2
>>>
>>> def cube(x):
...     return x ** 3
>>>
>>> def apply(data, fn):
...     return map(fn, data)
>>>
>>> def add(x, y):
...     return x + y
>>> data = (1, 2, 3, 4)
>>> transformations = [square, cube]
>>> result = reduce(apply, transformations, data)
>>> list(result)
[1, 64, 729, 4096]
>>> result = reduce(apply, transformations, data)
>>> reduce(add, result)
4890

11.3.11. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Functional Reduce Chain
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3

# %% English
# 1. Define `result` as a `range()` from 0 (inclusive) to 10 (exclusive)
# 2. Map `results` using `square` function
# 3. Filter `results` using `even` function
# 4. Reduce `results` using `product` function
# 5. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj `result` jako `range()` od 0 (włącznie) do 10 (rozłącznie)
# 2. Przemapuj `result` używając funkcji `square`
# 3. Przefiltruj `result` używając funkcji `even`
# 4. Zredukuj `result` używając funkcji `product`
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `range()`
# - `map()`
# - `filter()`
# - `reduce()`

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert isfunction(even), \
'Object `even` must be a function'
>>> assert isfunction(square), \
'Object `square` must be a function'
>>> assert result is not Ellipsis, \
'Assign result to variable: `result`'
>>> assert type(result) is int, \
'Variable `result` has invalid type, should be int'

>>> result
147456
"""
from functools import reduce


def even(x):
    return x % 2 == 0

def square(x):
    return x ** 2

def product(x, y):
    return x * y


# Define `result` as a `range()` from 0 (inclusive) to 10 (exclusive)
# type: range
result = ...

# Map `results` using `square` function
# type: map
result = ...

# Filter `results` using `even` function
# type: filter
result = ...

#  Reduce `results` using `product` function
# type: int
result = ...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Iterator Reduce Impl
# - Difficulty: medium
# - Lines: 5
# - Minutes: 13

# %% English
# 1. Write own implementation of a built-in `reduce()` function
# 2. Define function `myreduce` with parameters:
#    - parameter `function: Callable`
#    - parameter `iterable: list | tuple`
# 3. Don't validate arguments and assume, that user will:
#    - always pass valid type of arguments
#    - iterable length will always be greater than 0
# 4. Do not use: map, filter, zip, enumerate, all, any, reduce
# 5. Run doctests - all must succeed

# %% Polish
# 1. Zaimplementuj własne rozwiązanie wbudowanej funkcji `reduce()`
# 2. Zdefiniuj funkcję `myreduce` z parametrami:
#    - parameter `function: Callable`
#    - parameter `iterable: list | tuple`
# 3. Nie waliduj argumentów i przyjmij, że użytkownik:
#    - zawsze poda argumenty poprawnych typów
#    - długość iterable będzie większa od 0
# 4. Nie używaj: map, filter, zip, enumerate, all, any, reduce
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction
>>> from operator import add, mul
>>> assert isfunction(myreduce)

>>> myreduce(add, [1, 2, 3, 4, 5])
15

>>> myreduce(mul, [1, 2, 3, 4, 5])
120
"""

# Write own implementation of a built-in `reduce()` function
# Define function `myreduce` with parameters: `function`, `iterable`
# type: Callable[[Callable, Iterable], Any]
def myreduce(function, iterable):
    ...