Combining with reduce
map and filter turn a series into another series. reduce turns a whole series into a single value, such as a total. It isn't a built-in function: it lives in the module functools, so you import it first.
reduce(function, series, start) works like a loop with a running result. The result begins as start. For each item, reduce calls the function with two arguments, the result so far and the item, and what the function returns becomes the new result. After the last item, reduce returns that result. Here the result is a string that collects the first letter of each color:
from functools import reduce
colors = ["red", "green", "blue"]
code = reduce(lambda letters, color: letters + color[0], colors, "")
print(code)
rgbRound by round, the lambda gets the result so far and the next color, and what it returns is the result for the next round:
| Round | letters |
color |
The lambda returns |
|---|---|---|---|
| 1 | "" (the start) |
"red" |
"r" |
| 2 | "r" |
"green" |
"rg" |
| 3 | "rg" |
"blue" |
"rgb" |
There are no more colors after round 3, so reduce returns "rgb".
The function can combine the result and the item in any way you write, and the result doesn't have to be the same kind of value as the items. Both of these go through a list of words. The first keeps the longest word, and the second counts the letters of all the words:
from functools import reduce
words = ["map", "filter", "reduce", "lambda"]
print(reduce(lambda longest, word: word if len(word) > len(longest) else longest, words, ""))
print(reduce(lambda total, word: total + len(word), words, 0))
filter
21The start value is where the result begins, so choose one that fits the job. The longest word starts from the empty string, which any word is longer than, and a count of letters starts from 0. The start value is also what reduce returns for an empty list.
Without a start value
You can leave the start value out. Then reduce takes the first item as the starting result and goes on from the second one, so reduce(lambda a, b: a + b, [4, 8, 3]) starts from 4 and gives 15. But with an empty list there's nothing to start from, and reduce stops with TypeError: reduce() of empty iterable with no initial value.
reduce and your own objects
With objects, give the start value. The function adds something taken from each object to the result:
from functools import reduce
class Parcel:
def __init__(self, label, weight):
self.label = label
self.weight = weight
parcels = [Parcel("books", 4.5), Parcel("lamp", 2.0), Parcel("plates", 6.5)]
total = reduce(lambda result, parcel: result + parcel.weight, parcels, 0)
print("Total weight:", total)
Total weight: 13.0Without the 0, reduce would take the first parcel itself as the starting result, and the first round would try to add a weight to a Parcel object:
from functools import reduce
class Parcel:
def __init__(self, label, weight):
self.label = label
self.weight = weight
parcels = [Parcel("books", 4.5), Parcel("lamp", 2.0), Parcel("plates", 6.5)]
total = reduce(lambda result, parcel: result + parcel.weight, parcels)
print("Total weight:", total)
Traceback (most recent call last):
File "main.py", line 11, in <module>
total = reduce(lambda result, parcel: result + parcel.weight, parcels)
File "main.py", line 11, in <lambda>
total = reduce(lambda result, parcel: result + parcel.weight, parcels)
~~~~~~~^~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for +: 'Parcel' and 'float'So when the items are objects and the result is a number, always give a start value of the right type.