Permutations algoritm?

Duncan Smith buzzard at urubu.freeserve.co.uk
Fri Nov 15 18:50:57 EST 2002


"Stuart D. Gathman" <stuart at bmsi.com> wrote in message
news:ar3rgb$cn8$1 at nntp2-cm.news.eni.net...
> On Fri, 15 Nov 2002 15:18:25 -0500, sismex01 wrote:
>
> > Does anybody have a clear, simple, permutations algorithm in Python?
>
> If you Google (google.com) comp.lang.python for "Python Permutation",
> you will get lots of discussion.
>
> However, I'll copy in the algorithm I liked best for permuting an
> an arbitrary list: (STL algorithm works great on range(n))
>
> Alex Martelli wrote:
>
> *blink* -- yes... nonobvious but it works -- slicing the time to
> permute range(10) [need to start using more substantial examples
> here:-)] from 19.63 to 12.22 on my box.  However, the same trick
> also works for the modifying-in-place "myperm", slicing ITS time
> for permuting range(10) from 14.10 down to 10.13 (I'm only using
> Python 2.3 now for all of these measurements).
>
> And, applying Anton's trick to Duncan's best micro-optimization
> of his in-place-modification approach:
>
> def mypermy(l):
>     pop, insert, append = l.pop, l.insert, l.append
>
>     def halfperm():
>         ll = l
>         llen = len(ll)
>         if llen <= 2:
>             yield ll
>             return
>         aRange = range(llen)
>         v = pop()
>         for p in halfperm():
>             for j in aRange:
>                 insert(j, v)
>                 yield ll
>                 del ll[j]
>         append(v)
>
>     for h in halfperm():
>         yield h
>         h.reverse()
>         yield h
>         h.reverse()
>
> ITS time to permute range(10) is reduced from 12.13 to 9.59
> Yes we do need two .reverse calls -- halfperm's dependent
> on the list not being modified under it -- the alternative
> way to write the final loop:
>
>     for h in halfperm():
>         p = h[:]
>         yield p
>         p.reverse()
>         yield p
>
> takes 11.03 to permute range(10), once again showing (here,
> to me at least, less surprisingly) that modifying in-place
> and undoing the modification is faster than making a new
> list (a copy) and modifying the copy.
>
>
> Alex
>
>
> --
> Stuart D. Gathman <stuart at bmsi.com>
> Business Management Systems Inc.  Phone: 703 591-0911 Fax: 703 591-6154
> "Confutatis maledictis, flamis acribus addictis" - Mozart background
> song for the Microsoft "Where do you want to go from here?" commercial.

I'm not sure what I might have posted on this before, but I spent some time
on this a while back.  I'm pretty sure I was using a lex ordering.  I was
only concerned with the combinations of integers in the range 1 to n, but
probably easy enough to adapt.

---------------------------------------------------------------
def combinations(n, k):
    current = range(1, k + 1)
    max = range(n - k + 1, n + 1)
    while current != max:
        for number in current:
            print '%2d'% (number),
        print
        index = -1
        if current[index] != max[index]:
            current[index] = current[index] + 1
        else:
            for indices in range(-2, -k - 1, -1):
                if current[indices] != max[indices]:
                    current[indices] = current[indices] + 1
                    for other_indices in range(indices + 1, 0):
                        current[other_indices] = current[indices] +
other_indices - indices
                    break
    for number in max:
        print '%2d'% (number),
--------------------------------------------------------------------

eg.

>>> combinations(6,3)
 1  2  3
 1  2  4
 1  2  5
 1  2  6
 1  3  4
 1  3  5
 1  3  6
 1  4  5
 1  4  6
 1  5  6
 2  3  4
 2  3  5
 2  3  6
 2  4  5
 2  4  6
 2  5  6
 3  4  5
 3  4  6
 3  5  6
 4  5  6
>>>

Or doing some bit-shifting to speed it up a bit.  Different output, but the
idea is reasonably obvious (although it now appears to be reverse lex
ordering or some-such; it's a while since I wrote this).

----------------------------------------------------------
def combinations2(n, k):
    comb = gmpy.mpz(1)
    for i in range(k):
        comb = comb.setbit(i)  #first combination
    count = 1
    while count != k:
        print comb.digits(2)
        s_len = len(comb.digits(2)) - 1
        for i in range(s_len, n-1):
            comb = comb.setbit(i, 0)
            comb = comb.setbit(i+1)
            print comb.digits(2)
        count = 0
        for i in range(n-1, 0, -1):
            if comb.getbit(i) == 1:
                comb = comb.setbit(i, 0)
                count += 1
            else:
                s_len = len(comb.digits(2)) - 1
                comb = comb.setbit(s_len, 0)
                for j in range(count + 1):
                    comb = comb.setbit(s_len + j + 1)
                break

---------------------------------------------------------

>>> bit_stuff.combinations2(6,3)
111
1011
10011
100011
1101
10101
100101
11001
101001
110001
1110
10110
100110
11010
101010
110010
11100
101100
110100
111000
>>>
------------------------------------------------------------------

Comments on my crappy code are welcome.

Duncan







More information about the Python-list mailing list