ActiveState Code

Recipe 190465: Generator for permutations, combinations, selections of a sequence


Permutations and combinations are often required in algorithms that do a complete search of the solution space. They are typically rather large so it's best not to compute them entirely but better to lazily generate them. This recipe uses Python 2.2 generators to create appropriate generator objects, that can be use for example as ranges in for loops.

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/usr/bin/env python

__version__ = "1.0"

"""xpermutations.py
Generators for calculating a) the permutations of a sequence and
b) the combinations and selections of a number of elements from a
sequence. Uses Python 2.2 generators.

Similar solutions found also in comp.lang.python

Keywords: generator, combination, permutation, selection

See also: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/105962
See also: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66463
See also: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66465
"""

from __future__ import generators

def xcombinations(items, n):
    if n==0: yield []
    else:
        for i in xrange(len(items)):
            for cc in xcombinations(items[:i]+items[i+1:],n-1):
                yield [items[i]]+cc

def xuniqueCombinations(items, n):
    if n==0: yield []
    else:
        for i in xrange(len(items)):
            for cc in xuniqueCombinations(items[i+1:],n-1):
                yield [items[i]]+cc
            
def xselections(items, n):
    if n==0: yield []
    else:
        for i in xrange(len(items)):
            for ss in xselections(items, n-1):
                yield [items[i]]+ss

def xpermutations(items):
    return xcombinations(items, len(items))

if __name__=="__main__":
    print "Permutations of 'love'"
    for p in xpermutations(['l','o','v','e']): print ''.join(p)

    print
    print "Combinations of 2 letters from 'love'"
    for c in xcombinations(['l','o','v','e'],2): print ''.join(c)

    print
    print "Unique Combinations of 2 letters from 'love'"
    for uc in xuniqueCombinations(['l','o','v','e'],2): print ''.join(uc)

    print
    print "Selections of 2 letters from 'love'"
    for s in xselections(['l','o','v','e'],2): print ''.join(s)

    print
    print map(''.join, list(xpermutations('done')))

Discussion

This recipe provides both combinations and permutations and lazily generates them. You can do arbitrary calculations on the permutation/combination items not just print them.

If you require the complete list of permutations, just use the built-in list() operator. Note that the resulting list can be huge.

All x-generators defined here yield sequences with elements from the original sequence. Their difference is in which elements they take:

xpermutations takes all elements from the sequence, order matters.

xcombinations takes n distinct elements from the sequence, order matters.

xuniqueCombinations takes n distinct elements from the sequence, order is irrelevant.

xselections takes n elements (not necessarily distinct) from the sequence, order matters.

Note that 'distinct' means "different elements in the orginal sequence" and not "different value", i.e.

list(xuniqueCombinations('aabb',2)) is

[['a', 'a'], ['a', 'b'], ['a', 'b'], ['a', 'b'], ['a', 'b'], ['b', 'b']]

and not

[['a', 'b']].

If your sequence has only items with unique values, you won't notice the difference (no pun intended).

Comments

  1. 1. At noon on 17 jul 2003, Guy Argo said:

    A simple refactoring. I notice that the bodies of xcombinations, xuniqueCombinations, xselections are almost identical. We could refactor as follows:

    def generalized(f, items, n):
        if n==0: yield []
        else:
            for i in xrange(len(items)):
                for cc in generalized(f(items, i), n-1):
                    yield [items[i]]+cc
    
    def skipIthItem(items, i):
        return items[:i]+items[i+1:]
    
    def afterIthItem(items, i):
        return items[i+1:]
    
    def keepAllItems(items, i):
        return items
    
    def xcombinations(items, n):
        return generalized(skipIthItem, items, n)
    
    def xuniqueCombinations(items, n):
        return generalized(afterIthItem, items, n)
    
    def xselections(items, n):
        return generalized(keepAllItems, items, n)
    
  2. 2. At 10:58 p.m. on 18 sep 2004, Li Daobing said:

    A faster xuniqueCombinations algorithm.

    def xuniqueCombinations(items, n):
        if n==0: yield []
        else:
            for i in xrange(len(items)-n+1):
                for cc in xuniqueCombinations(items[i+1:],n-1):
                    yield [items[i]]+cc
    
  3. 3. At 2:08 p.m. on 30 mar 2006, Connelly Barnes said:

    Faster permutations.

    def permutations(L):
        if len(L) == 1:
            yield [L[0]]
        elif len(L) >= 2:
            (a, b) = (L[0:1], L[1:])
            for p in permutations(b):
                for i in range(len(p)+1):
                    yield b[:i] + a + b[i:]
    

    The above is 6x faster on my Pentium P3 3.0 GHz, Python 2.4.1.

  4. 4. At 2:10 p.m. on 30 mar 2006, Connelly Barnes said:

    Appendum. I meant to say: 6x faster for computing list(permutations(range(8))).

  5. 5. At 2:06 a.m. on 13 apr 2006, Connelly Barnes said:

    Err. On the last line that should have been yield p[:i] + a + p[i:] .

  6. 6. At 8:37 a.m. on 20 feb 2007, Simone Leo said:

    much faster... ... But it only works on lists:

    def permutations(L):
        if len(L) <= 1:
            yield L
        else:
            a = [L.pop(0)]
            for p in permutations(L):
                for i in range(len(p)+1):
                    yield p[:i] + a + p[i:]
    
  7. 7. At 3:25 p.m. on 14 jun 2007, paul stadfeld said:

    Wrong terminology.

    Unfortunately, it's a very poor example. The terminology is
    all wrong.
    
    "xpermutations takes all elements from the sequence, order matters."
    This ought to be the Cartesian Product, but it's not (no replacement).
    
    "xcombinations takes n distinct elements from the sequence, order
    matters."
    If order matters, it's a PERMUTATION, period.
    
    "xuniqueCombinations takes n distinct elements from the sequence,
    order is irrelevant."
    No such thing, a Combination is unique by definition.
    
    "xselections takes n elements (not necessarily distinct) from the
    sequence, order matters."
    Ah, this allows a size operator, so if size = length, we get full
    Cartesian Product.
    
    The proper terminology for the Cartesian Product and
    its subsets is:
    
    Permutations with replacement
    Combinations with replacement
    Permutations without replacement
    Combinations without replacement
    
    And if the functions were properly labeled, you would get:
    
        permutation without replacement - size 4
    
    Permutations of 'love'
    love loev lvoe lveo leov levo olve olev ovle ovel oelv oevl vloe vleo
    vole voel velo veol elov elvo eolv eovl evlo evol
    
    
        permutation without replacement - size 2
    
    Combinations of 2 letters from 'love'
    lo lv le ol ov oe vl vo ve el eo ev
    
    
        combination without replacement - size 2
    
    Unique Combinations of 2 letters from 'love'
    lo lv le ov oe ve
    
    
        permutation with replacement - size 2
    
    Selections of 2 letters from 'love'
    ll lo lv le ol oo ov oe vl vo vv ve el eo ev ee
    
    
        full Cartesian Product, permutations with replacement - size 4
    
    Selections of 4 letters from 'love'
    llll lllo lllv llle llol lloo llov lloe llvl llvo llvv llve llel lleo
    llev llee loll lolo lolv lole lool looo loov looe lovl lovo lovv love
    loel loeo loev loee lvll lvlo lvlv lvle lvol lvoo lvov lvoe lvvl lvvo
    lvvv lvve lvel lveo lvev lvee lell lelo lelv lele leol leoo leov leoe
    levl levo levv leve leel leeo leev leee olll ollo ollv olle olol oloo
    olov oloe olvl olvo olvv olve olel oleo olev olee ooll oolo oolv oole
    oool oooo ooov oooe oovl oovo oovv oove ooel ooeo ooev ooee ovll ovlo
    ovlv ovle ovol ovoo ovov ovoe ovvl ovvo ovvv ovve ovel oveo ovev ovee
    oell oelo oelv oele oeol oeoo oeov oeoe oevl oevo oevv oeve oeel oeeo
    oeev oeee vlll vllo vllv vlle vlol vloo vlov vloe vlvl vlvo vlvv vlve
    vlel vleo vlev vlee voll volo volv vole vool vooo voov vooe vovl vovo
    vovv vove voel voeo voev voee vvll vvlo vvlv vvle vvol vvoo vvov vvoe
    vvvl vvvo vvvv vvve vvel vveo vvev vvee vell velo velv vele veol veoo
    veov veoe vevl vevo vevv veve veel veeo veev veee elll ello ellv elle
    elol eloo elov eloe elvl elvo elvv elve elel eleo elev elee eoll eolo
    eolv eole eool eooo eoov eooe eovl eovo eovv eove eoel eoeo eoev eoee
    

    (comment continued...)

  8. 8. At 3:25 p.m. on 14 jun 2007, paul stadfeld said:

    (...continued from previous comment)

    evll evlo evlv evle evol evoo evov evoe evvl evvo evvv evve evel eveo
    evev evee eell eelo eelv eele eeol eeoo eeov eeoe eevl eevo eevv eeve
    eeel eeeo eeev eeee
    
    
    And Combinations with replacement seems to be missing.
    
  9. 9. At 4:23 a.m. on 30 jul 2008, Benjamin Sergeant said:

    Comment number 4 code is not working for S = [0,1,2,3]. Comment number 6 code works.

Sign in to comment