ActiveState Code

Recipe 476215: reducipes: Excuses to use the reduce built-in


reduce is a pretty nice built-in, but its usage in everyday code is seemingly rare. If you've been looking for excuses to use reduce, here are a few useful recipes to get you started.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import operator

def factorial(n):
    """Calculate n factorial"""
    return reduce(operator.mul, range(2, n+1), 1)

def intersection(*sets):
    """Get the intersection of all input sets"""
    return reduce(set.intersection, sets)

def union(*sets):
    """Get the union of all input sets"""
    return reduce(set.union, sets)

def join(*seqs):
    """Join any input sequences that support concatenation"""
    return reduce(operator.concat, seqs)

"""
Some usage:
    
>>> factorial(3)
6

>>> factorial(10)
3628800

>>> a = set([1, 2, 3, 4, 5])
>>> b = set([5, 6, 3, 7])
>>> c = set([8, 7, 5])
>>> intersection(a, b, c)
set([5])

>>> union(a, b, c)
set([1, 2, 3, 4, 5, 6, 7, 8])

>>> join("one", "two", "three", "four")
'onetwothreefour'

>>> join([1, 2, 3], [5, 6], [6, 7])
[1, 2, 3, 4, 5, 6, 7]
"""

Discussion

If you post a clever way to use reduce to solve a common problem, post it here and I'll add it to the recipe.

Comments

  1. 1. At 11:47 a.m. on 31 mar 2006, Brandon Beck said:

    Chained replacements in a string. One way that I've recently found to use reduce is for the chained replacement of characters in a string.

    So instead of:

    def replace(text, replacements):
        for (old, new) in replacements:
            text = text.replace(old, new)
    
        return text
    

    I use:

    def replace(text, replacements):
        return reduce(lambda s, (old, new): s.replace(old, new), replacements, text)
    

    And an example:

    replace("This is a normal string", [(c, c.upper()) for c in "aeiou"])
    'ThIs Is A nOrmAl strIng'
    

    Ironically enough, on my machine the the reduce version is about 37% slower than the looped version on the example I gave above.

  2. 2. At 10:03 a.m. on 21 apr 2006, Frank P Mora said:

    Anonymous in-line list comprehension functions. Python is a wonderfully expressive programming language. Some expressions such as those involving reduce, are more elegant and simple than others. I enjoy and value the functional facilities and functions as first class objects in Python. I have, however, seen evidence that some of these are in jeopardy (google for “The fate of reduce() in Python”.)

    Here are in-line list comprehension versions of your functions and a bit more. The general form is: set initial value, set iteration values and successively apply an infix or prefix function cumulatively and finally take the last value of the generated list.

    >>> T=[set(s) for s in [(1,2,3,4,5),(2,3,4,5,6),(3,4,5,6,7),(4,5,6,7,8)]]
    
    >>> f=set.intersection
    >>> [ s for s in [f(*T[:2])] for i in range(2,len(T)) for s in [f(s,T[i])] ][-1]
    set([4, 5])     ## issues the null set if no intersection between all given sets
    
    >>> f=set.union
    >>> [ s for s in [f(*T[:2])] for i in range(2,len(T)) for s in [f(s,T[i])] ][-1]
    set([1, 2, 3, 4, 5, 6, 7, 8])
    
    >>> # join
    >>> T=[(1,2,3,4,5,6,7),(3,4,5,6,7),(5,6,7),(7,8,9,2,1,5)]
    
    >>> [ s for s in [T[0]] for i in range(1,len(T)) for s in [s + T[i]] ][-1]
    (1, 2, 3, 4, 5, 6, 7, 3, 4, 5, 6, 7, 5, 6, 7, 7, 8, 9, 2, 1, 5)
    
    >>> t=["abcd","efgh","ijkl","mnop","qrst"]
    
    >>> [ s for s in [T[0]] for i in range(1,len(T)) for s in [s + T[i]] ][-1]
    'abcdefghijklmnopqrst'
    
    >>> # factorial
    >>> fac = 6
    >>> [j for j in [1] for i in range(2, fac+1) for j in [j*i]][-1]
    720
    
    >>> nl = [2, 3, 6, 18]
    
    >>> # sum
    >>> [j for j in [0] for i in nl for j in [j + i]][-1]
    29
    >>> # product
    >>> [j for j in [1] for i in nl for j in [j * i]][-1]
    648
    >>>
    
    Much thanks to Steven Bethard for the cleaner form of this pattern.
    
  3. 3. At 10:28 a.m. on 22 apr 2006, Frank P Mora said:

    And for predicates.

    >>> l=(20,30,40,50,60,70,80,90,1,2,3,4,5)
    
    >>> # all function
    >>> [ p for p in [l[0]>42] for i in range(1,len(l)) for p in [p and l[i]>42] ][-1]
    False
    
    >>> # any function
    >>> [ p for p in [l[0]>42] for i in range(1,len(l)) for p in [p  or l[i]>42] ][-1]
    True
    
    The pattern *is* very general and regular
    
  4. 4. At 10:01 a.m. on 20 apr 2007, Paul McGuire said:

    Use reduce to call divmod with succesive divisors. I used reduce to build up a tuple of the parts of an elapsed time, starting with a floating point number of seconds. Posted at recipe: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511486

    -- Paul

Sign in to comment