Permutation Generator
Gerard Flanagan
grflanagan at yahoo.co.uk
Mon Aug 15 11:27:07 EDT 2005
Talin wrote:
> I'm sure I am not the first person to do this, but I wanted to share
> this: a generator which returns all permutations of a list:
>
> def permute( lst ):
> if len( lst ) == 1:
> yield lst
> else:
> head = lst[:1]
> for x in permute( lst[1:] ):
> yield head + x
> yield x + head
> return
>
> -- Talin
As it happens, I've just started learning Python and my 'Hello World'
was a class which generates all permutations of the set {1,2,...n}.
It's a translation of some Pascal code from the book "Programming for
Mathematicians" by Raymond Seroul (2000 Springer-Verlag), and produces
a (non-lexicographic) total ordering of the set using Johnson's
Algorithm.
To explain the algorithm briefly. Each element is 'adorned' with a flag
( either 1 or -1 ) which determines the direction that an element is
'looking' - so, given the sequence
[ [-1,3], [1,2], [1,1], [-1,4] ]
1 sees 4
2 sees 1
3 sees nothing
4 sees 1
(with the convention that -1 means 'looking left')
An element is said to be 'mobile' if it is looking at a smaller number.
eg. in the sequence above, both 2 and 4 are mobile.
Then the algorithm is:
- find the highest mobile
- swap this mobile with the element it sees, but don't swap their
direction flags
- reverse the direction flags of any element larger than the mobile
In coding the algorithm, sentinels with value 1 bigger than n are added
at either end of the sequence, this helps when checking which elements
are mobile. Also, 1 and -1 aren't arbitrary flags, these values are
used because you can find which element another element 'sees' by using
'index + flag'.
Here's the code:
def factorial( n ):
if n <= 1:
return 1
else:
return n * factorial( n-1 )
class Permutation:
def __init__( self, items ):
seq = list( items[:] )
n = len( seq )
self.sequence = seq
self.length = n
self.count = factorial( n )
def __getitem__( self, key ):
result = []
sequence = self.sequence[:]
N = self.count
index = key
for i in range( self.length, 0, -1):
N = N / i
choice, index = index // N, index % N
result += [ sequence.pop(choice) ]
return result
class NPermutation( Permutation ):
def __init__( self, n ):
Permutation.__init__( self, range( 1, n+1 ) )
self.reset()
def reset( self ):
list = [ [-1,i] for i in range(self.length+2) ]
list[0][1] = self.length+1
#eg. n=3 -> list = [[-1,4], [-1,1], [-1,2], [-1,3], [-1,4]]
self.__current = list
self.__index = 0
def __iter__( self ):
return self
def next( self ):
if self.__index == self.count:
self.reset()
raise StopIteration
elif self.__index > 0:
j = self.__get_index_of_highest_mobile()
#remember the mobile itself before you move it
mobile = self.__current[j][1]
#swap the mobile with the element it 'sees'
self.__move_mobile_at_index( j )
#switch the direction of elements greater than mobile
if mobile < self.length:
self.__reorient_list_elements( mobile )
self.__index += 1
return [ a[1] for a in self.__current[ 1:self.length+1 ] ]
def __get_index_of_highest_mobile( self ):
high_value = 0
high_index = 0
for i in range( 1, self.length+1 ):
direction = self.__current[i][0]
value = self.__current[i][1]
if value > high_value and value >
self.__current[i+direction][1]:
high_value = value
high_index = i
return high_index
def __move_mobile_at_index( self, index ):
direction = self.__current[index][0]
value = self.__current[index][1]
self.__current[index] = self.__current[index+direction]
self.__current[index+direction] = [direction,value]
def __reorient_list_elements( self, mobile ):
for i in range( 1, self.length+1 ):
if self.__current[i][1] > mobile:
self.__current[i][0] = -self.__current[i][0]
x = NPermutation( 6 )
print 'loop with __getitem__'
print '---------------'
for i in range( x.count ):
print x[i]
print 'loop with __iter__'
print '---------------'
for perm in x:
print perm
Gerard Flanagan
15/8/05
More information about the Python-list
mailing list