This class is a generator that recursively calls itself (self.next()
).
It is a bit odd, because when you ask the generator for all its values, it doesn't really follow the iteration protocol by returning self
(an object with a .next()
function) like you'd expect. Rather, it returns the result of self.next()
, which is quite poor wording. The next
function should rather just be called permutations()
, because the next
function really has nothing whatsoever to do with the fact that a next
function is required in the iteration protocol.
Anyway, you can see the recursive trace of this if you augment the function with a recursion depth parameter:
class Permutation:
def __init__(self,justalist):
self._data = justalist[:]
self._sofar = []
def __iter__(self):
return self.next()
def next(self, depth=0):
debug = lambda text:print('\t'*depth+' '+text)
for elem in self._data:
#print( 'self._data: {}'.format(self._data) )
#print( 'self._sofar: {}'.format(self._sofar) )
if elem not in self._sofar:
self._sofar.append(elem)
if len(self._sofar) == len(self._data):
debug( 'yielding self._sofar[:]: {}'.format(self._sofar[:]) )
yield self._sofar[:]
else:
for v in self.next(depth+1):
debug( 'yielding elements of self.next() one-by-one: {}'.format(v) )
yield v
self._sofar.pop()
Demo:
>>> list( Permutation(range(4)) )
yielding self._sofar[:]: [0, 1, 2, 3]
yielding elements of self.next() one-by-one: [0, 1, 2, 3]
yielding elements of self.next() one-by-one: [0, 1, 2, 3]
yielding elements of self.next() one-by-one: [0, 1, 2, 3]
yielding self._sofar[:]: [0, 1, 3, 2]
yielding elements of self.next() one-by-one: [0, 1, 3, 2]
yielding elements of self.next() one-by-one: [0, 1, 3, 2]
yielding elements of self.next() one-by-one: [0, 1, 3, 2]
yielding self._sofar[:]: [0, 2, 1, 3]
yielding elements of self.next() one-by-one: [0, 2, 1, 3]
yielding elements of self.next() one-by-one: [0, 2, 1, 3]
yielding elements of self.next() one-by-one: [0, 2, 1, 3]
yielding self._sofar[:]: [0, 2, 3, 1]
yielding elements of self.next() one-by-one: [0, 2, 3, 1]
yielding elements of self.next() one-by-one: [0, 2, 3, 1]
yielding elements of self.next() one-by-one: [0, 2, 3, 1]
yielding self._sofar[:]: [0, 3, 1, 2]
yielding elements of self.next() one-by-one: [0, 3, 1, 2]
yielding elements of self.next() one-by-one: [0, 3, 1, 2]
yielding elements of self.next() one-by-one: [0, 3, 1, 2]
yielding self._sofar[:]: [0, 3, 2, 1]
yielding elements of self.next() one-by-one: [0, 3, 2, 1]
yielding elements of self.next() one-by-one: [0, 3, 2, 1]
yielding elements of self.next() one-by-one: [0, 3, 2, 1]
yielding self._sofar[:]: [1, 0, 2, 3]
yielding elements of self.next() one-by-one: [1, 0, 2, 3]
yielding elements of self.next() one-by-one: [1, 0, 2, 3]
yielding elements of self.next() one-by-one: [1, 0, 2, 3]
As you can see, the flow of the data is as follows: recursion proceeds to the deepest level (4 in this case, len([0,1,2,3])
is 4), produces a permutation, and yields it back up to the previous level. That level yields it back up to the previous level, etc. At the highest level, the yield returns it.
In conclusion, this is a broken and terrible implementation, since:
- It could just be accomplished by a recursive functional pattern with this extra Class nonsense
- It defines
next()
in a way that strongly implies something, but actually does something else.
- It will fail if any values are duplicated, e.g.
Permutation([0,1,1])
will fail
You should just use http://docs.python.org/library/itertools.html#itertools.permutations (from itertools import permutations
)