1
class Permutation:
    def __init__(self,justalist):
        self._data = justalist[:]
        self._sofar = []
    def __iter__(self):
        return self.next()
    def next(self):
        for elem in self._data:
            if elem not in self._sofar:
                self._sofar.append(elem)
                if len(self._sofar) == len(self._data):
                    yield self._sofar[:]
                else:
                    for v in self.next():
                        yield v
                self._sofar.pop()

Found this code online ( http://users.softlab.ntua.gr/~ttsiod/yield.html ). Tried to figure it out, but haven't made much headway. The part that is really tripping me up is the:

                 for v in self.next():
                    yield v
                 self._sofar.pop()

To code for the class is run by doing:

      for i in Permutation(a):
         print(i)

I've read this:

"by invoking yield, the next member is now a generator function, so we can't just simply call next again - if we did, we would lose the returned generator object, and thus, the returned permutations! We must instead loop over the returned results with a simple for v in self.next(): yield v. This way, the results are properly propagated to the "parent" call."

I don't know if this explains what is going on, but it means nothing to me. (I thought I understood generators.)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
user678392
  • 1,981
  • 3
  • 28
  • 50
  • try playing a bit with `yield`, write a generator function or two and you'll figure it out. one of the coolest bits about python! – Not_a_Golfer Apr 30 '12 at 21:14

2 Answers2

3

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:

  1. It could just be accomplished by a recursive functional pattern with this extra Class nonsense
  2. It defines next() in a way that strongly implies something, but actually does something else.
  3. 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)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
1

The key idea is that Permutation.next() yields all permutations of the items in _data that begin with _sofar.

To do this, we try adding on each possible element onto the end of _sofar. There are then two cases: either _sofar is long enough (i.e., we've got a complete permutation), in which case we just yield a copy of it; or it isn't (i.e., we've got a partial permutation), in which case we recursively call next -- which, in general, yields lots of permutations, so we have to yield them on ourselves one by one.

Incidentally, I think the code would be neater and easier to understand if it went more like this (DANGER: untested code):

def next(self):
    if len(self._sofar) == len(self._data):
        yield self._sofar[:]
    else:
        for elem in self._data:
            if elem not in self._sofar:
                self._sofar.append(elem)
                for v in self.next():
                    yield v
                self._sofar.pop()

which also has the merit of giving the right answer when there are no elements (there's exactly one permutation, the empty sequence; the code as given yields none). There are a few optimizations I'd be inclined to make, too, but they aren't important.

Gareth McCaughan
  • 19,888
  • 1
  • 41
  • 62
  • McGaughan ... still having trouble. Are you saying that self.next() is recursively next(self)? – user678392 Apr 30 '12 at 22:09
  • supposed to be "recursively calling" next(self) – user678392 Apr 30 '12 at 22:15
  • Yup, self.next() calls self.next() recursively. What makes the recursion bottom out eventually is that each call has one more element in `_sofar` than its parent, and when `_sofar` gets "full" we don't make the recursive call. – Gareth McCaughan Apr 30 '12 at 22:19
  • Oh, now I understand your question. Sorry if my previous response missed the point. When you call a method on an object -- e.g., `self.next()` -- what happens behind the scenes is that the `next` function gets called with the object as its first argument. So `x.foo(1,2,3)` turns into `foo(x,1,2,3)`. So when you define a method, its first argument is always the object it's being applied to, and it's conventional to give that the name `self`. – Gareth McCaughan Apr 30 '12 at 22:38
  • (1) does self.next() call self(next) recursively. (2) how? given that one function has an argument and the other doesn't. Where is self.next() even defined? Furthermore, if I take a generator object (call it a), and I do a.next(), I get an error message. I have to call next(a). So how is self.next() even being run? – user678392 Apr 30 '12 at 22:41
  • There's a pretty good explanation of this stuff in AndiDog's answer to this SO question: http://stackoverflow.com/questions/3786881/what-is-a-method-in-python – Gareth McCaughan Apr 30 '12 at 22:41
  • (Sorry, that wasn't a response to your latest comment, which I hadn't seen when I posted it.) So, here's how it goes. (1) When you iterate over a `Permutation` object `p`, the magic of the iterator protocol means that `p.next()` gets called. That's the method whose definition begins `def next(self):`. (2) `p` gets passed to that as its first parameter; so inside that method, `self` is the object `p`. (3) Within that code there's a call to `self.next()`, which is calling the exact same method again, on the same object. – Gareth McCaughan Apr 30 '12 at 22:44
  • It's worth noting that, as ninjagecko points out, this class is *not* a good example of how the iterator protocol is supposed to work. – Gareth McCaughan Apr 30 '12 at 22:47