Reduce functions (also known as fold, accumulate, aggregate, compress, or inject in other languages) are very powerful, being open enough to practically anything you can think of for turning a collection into anything else. reduce
could even be used to implement the map
and filter
functions, making it the most powerful of the three basic collection transformation functions.
The Problem
But… it’s ugly. Even if you’re using the operator functions instead of lambdas, writing reduce functions is rarely easy to read. Let’s say I wanted to implement a factorial function using reduce (yes, I know I did factorials last week, but they’re easy and I didn’t want reimplement one of python’s prebuilt reducers like sum
or count
), it would look something like this:
def factorial1(n):
return functools.reduce(operator.mul, range(1, n + 1), 1)
It takes a while to reason about what the reduce function is doing, even with a decent amount of experience of dealing with reduce functions. You could use the keyword arguments to help out, like so:
def factorial2(n):
return functools.reduce(operator.mul, sequence=range(1, n + 1), initial=1)
But it doesn’t really make it a whole lot easier to read. A little, but not much. So, what can we do to fix this?
The Solution
We can take a little clue from what python already does for us. It gives us the sum
and count
functions for a reason. They clarify the reduce
function’s use to something that’s a lot easier to read. We should do the same with for our factorial function.
What we need is essentially the same thing as sum
, but with multiplication instead, so we’ll name our intermediary function product
. You could call it multiplyall
if you like, too. Then we just need to make the product
function take in a sequence and pass it to reduce
.
def product(seq):
return functools.reduce(operator.mul, seq, initial=1)
You could also implement it as a partial
:
product2 = functools.partial(functools.reduce, operator.mul, initial=1)
These product
functions each also suffer a little bit from lack of readability, but their name helps guide the reader to figure out what’s going on.
Let’s use this new function in our factorial
function, shall we?
def factorial3(n):
return product(range(1, n + 1))
That looks so much better!
Lesson Learned
Reduce is powerful, but ugly. Many (probably most, and possibly all) cases where reduce
is used, it should be partially implemented in an intermediate function that explains what’s happening. This is especially useful if you’re doing the same reduction operation in more than one place in your code.