Archived

reduce

An old note — I haven't updated it since I wrote it.

reduce begins to unlock some serious power from Python's standard library. Here's a dead-simple example of summing a list using reduce:

1from functools import reduce
2res = reduce(lambda acc, v: acc + v, [1,2,3,4,5])
3print(res) # 15

This is what's happening:

In ascii, you might draw the table like this:

1res   acc   v
2      1     2     [|1|,|2|, 3 , 4 , 5 ]
3      3     3     [ 1 , 2 ,|3|, 4 , 5 ]
4      6     4     [ 1 , 2 , 3 ,|4|, 5 ]
5      10    5     [ 1 , 2 , 3 , 4 ,|5|]
615

Remember that drawing these tables in ascii is a very quick way of gaining a complete understanding of whatever number crunching you are doing, and is probably the most effective and quickest method of all.

The lambda passed into reduce takes two arguments, i like calling the first one the accumulator and the second one the value. Initially they take the first two values of the list. After adding 1 + 2, the resulting 3 of that addition gets passed in to the acc, so the next computation is 3 + 3, then 6 + 4 and so on.

Of course, we could use a second lambda to implement an actual sum function:

1from functools import reduce
2sum = lambda A: reduce(lambda acc, v: acc + v, A)
3print(sum([1,2,3,4,5])) # 15

Alternatively we could use operator.add and operator.mult to implement sum and product functions.

1from functools import reduce
2from operator import add, mul
3sum =  lambda A: reduce(add, A)
4prod = lambda A: reduce(mul, A)
5print(sum([1,2,3,4,5])) # 15
6print(prod([1,2,3,4,5])) # 120

Starting value

On the first iteration, reduce uses the first two numbers in A for the values of a and b in the lambda. In some cases, you'll want to control the initial value of a using reduce's third argument, while b receives the first value of A:

1from functools import reduce
2sum =  lambda A: reduce(add, A, 0)
3print(sum([1,2,3,4,5])) # 15

You'll soon see why this can be incredibly useful.

Find the missing number in array

Q: Given a list of numbers from 0 -> n missing 1 number, find the missing number in O(1) space and O(n) time. (leetcode link).

Solution:

Taking the xor of all the numbers in the array we're given, and taking the xor of all the numbers in range(n + 1) and xoring these two results together gives us the missing number:

1from functools import reduce
2from operator import ixor
3
4find_missing = lambda A: ixor(reduce(ixor, A), reduce(ixor, range(len(A) + 1)))
5find_missing([5, 1, 2, 3, 4, 0, 6, 7, 8, 10]) # result 9

Roman Numerals

Here's how to solve the roman numeral to integer problem using reduce: the key difficulty is that if the current number, e.g I is less than the following number V it has to be subtracted, not added.

Let's begin by initialising our roman to int lookup dictionary, using the dict constructor. When keys don't start with a number, and doesn't contain inconvenient characters, using dict can be very hand and compact.

1from functools import reduce
2S = dict(I=1, V=5, X=10, L=50, C=100, D=500, M=1000)

Next, let's take a look at our reduce. Let's ignore the lambda for now. The sequence we're passing in is the range(len(s)-1), so in the case of XIV: the sequence [1,0]. The third value we pass into reduce is that actual integer value of the last character (5).

1reduce(lambda a, b: None, reversed(range(len(s)-1)), S[s[-1]])
 1from functools import reduce
 2from operator import add, sub
 3
 4S = dict(I=1, V=5, X=10, L=50, C=100, D=500, M=1000)
 5
 6def romanToInt(s):
 7    def func(a, b):
 8        if S[s[b+1]] > S[s[b]]:
 9            return a - S[s[b]]
10        else:
11            return a + S[s[b]]
12    return reduce(func, reversed(range(len(s)-1)), S[s[-1]])
13
14print(romanToInt('XIV')) # 14
 1from functools import reduce
 2from operator import add, sub
 3
 4S = dict(I=1, V=5, X=10, L=50, C=100, D=500, M=1000)
 5
 6def romanToInt(s):
 7    r = lambda a, b: (add, sub)[S[s[b+1]] > S[s[b]]](a, S[s[b]])
 8    return reduce(r, reversed(range(len(s)-1)), S[s[-1]])
 9
10print(romanToInt('XIV')) # 14