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
filter functions, making it the most powerful of the three basic collection transformation functions.
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
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?
We can take a little clue from what python already does for us. It gives us the
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
def product(seq): return functools.reduce(operator.mul, seq, initial=1)
You could also implement it as a
product2 = functools.partial(functools.reduce, operator.mul, initial=1)
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!
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.