Recursive function to develop permutations

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Steve Goldman

    Recursive function to develop permutations

    Hi,

    I am trying to come up with a way to develop all n-length permutations of a
    given list of values. The short function below seems to work, but I can't
    help thinking there's a better way. Not being a computer scientist, I find
    recursive functions to be frightening and unnatural. I'd appreciate if
    anyone can tell me the pythonic idiom to accomplish this.

    Thanks for your help,

    Steve Goldman

    ###START CODE###

    def permute(list,n, initialize=True ):
    """A recursive function that returns a list of all length n ordered
    permutations of "list", length k."""
    if initialize:
    global permutation, permutation_lis t
    permutation=[] #This list holds the individual permutations. It
    is built one element at a time
    permutation_lis t=[] #This list is the list that will contain all
    the permutations
    n=n-1 #This counts down for each element of the permutation. It is a
    local variable,
    #so each subsequent function call has n reduced by one
    for e in list:
    permutation.app end(e)
    if n>0: #that is,the permutation needs additional elements
    permute(list,n, False)
    else: # the permutation is length n
    permutation_lis t.append(permut ation[:]) #store the completed
    permutation
    permutation.pop (-1) # knock off the last value and go to the next
    element in "list"
    return permutation_lis t


    if __name__=='__ma in__':
    list=['red','white',' blue','green']
    print permute(list,3)



  • Miki Tebeka

    #2
    Re: Recursive function to develop permutations

    Hello Steve,
    [color=blue]
    > I am trying to come up with a way to develop all n-length permutations of a
    > given list of values.
    > ###START CODE###
    > ...
    >[/color]
    Side note:
    Please make sure that when posting to a newgroup your lines are less than
    78 charcters long.

    I'd split the problem (If I understand it correctly) to two problems:
    1. Find all sublist of size n of l
    2. Find all permutations of a list l

    And using generators is always fun :-)

    Something in the lines of:
    #!/usr/bin/env python

    def sublists(l, n):
    '''All sublists in length 'n' of 'l' '''
    assert(len(l) >= n)

    if len(l) == n: # Recursion base
    yield l
    return

    for i in range(len(l)): # Remove one item and recurse
    for sub in sublists(l[:i] + l[i+1:], n):
    yield sub

    def permutations(l) :
    '''All permuatations of 'l' '''
    if len(l) == 1: # Rercursion base
    yield l
    return

    for i, v in enumerate(l):
    for p in permutations(l[:i] + l[i+1:]):
    yield [v] + p

    def subperm(l, n):
    '''All permutation of sublists in size 'n' of 'l' '''
    for s in sublists(l, n):
    for p in permutations(s) :
    yield p

    if __name__ == "__main__":
    from sys import argv

    l = range(int(argv[1]))
    n = int(argv[2])

    for p in subperm(l, n):
    print p

    HTH.
    --
    ------------------------------------------------------------------------
    Miki Tebeka <miki.tebeka@gm ail.com>

    The only difference between children and adults is the price of the toys

    Comment

    • Jack Diederich

      #3
      Re: Recursive function to develop permutations

      On Tue, Oct 19, 2004 at 12:34:57AM +0000, Steve Goldman wrote:[color=blue]
      > Hi,
      >
      > I am trying to come up with a way to develop all n-length permutations of a
      > given list of values. The short function below seems to work, but I can't
      > help thinking there's a better way. Not being a computer scientist, I find
      > recursive functions to be frightening and unnatural. I'd appreciate if
      > anyone can tell me the pythonic idiom to accomplish this.
      >
      > ###START CODE###[/color]
      [snip]

      Combinatorics (permutations, combinations, etc) get golfed (shortest/fastest)
      on c.l.py a couple times a year. Check groups.google.c om for some solutions.
      If you want fast, try probstat.sf.net which does some combinatorics in C with
      a python wrapper. disclosure: I wrote it.

      -Jack

      Comment

      • Alex Martelli

        #4
        Re: Recursive function to develop permutations

        Steve Goldman <steve_g@ix.net com.com> wrote:
        [color=blue]
        > Hi,
        >
        > I am trying to come up with a way to develop all n-length permutations of a
        > given list of values. The short function below seems to work, but I can't
        > help thinking there's a better way. Not being a computer scientist, I find
        > recursive functions to be frightening and unnatural. I'd appreciate if
        > anyone can tell me the pythonic idiom to accomplish this.[/color]

        The implementation of something that is naturally defined recursively is
        often simplest when done recursively (unless there's some manual
        equivalent of a "tail call optimization" that's easy and natural). And
        many things in combinatorics are generally subject to naturally
        recursive definitions.

        If you hope to find a natural and smooth non-recursive implementation,
        think first about a non-recursive definition (the alternative is putting
        the recursion in, then removing it by explicitly introducing a stack; it
        works, but is not necessarily pretty).

        In this case it seems to me that you're lucky: there IS a non-recursive
        definition of "permutatio ns of length N out of a list of X values". It
        may go something like...:
        each result is a container with N slots. each slot, independently,
        assumes each of the X values, yielding X**N results

        This is like saying that (if the X values are digits in base X) the
        result is a number counting in base X from 0 to X**N-1. In fact, the X
        values may be arbitrary, but the INDICES of the values in the list that
        holds them ARE the numbers 0 to X-1, i.e., exactly the digits in base X.

        So, we have reduced the problem to one of counting in an arbitrary base,
        so it becomes pretty easy to solve non-recursively. For example:

        def permute(Xs, N):
        permutations = []
        permutation = [None] * N
        X = len(Xs)
        for i in xrange(X**N):
        for j in xrange(N):
        i, k = divmod(i, X)
        permutation[j] = Xs[k]
        permutations.ap pend(list(permu tation))
        return permutations

        Here, we're using i as an actual count, an integer, and the inner loop
        does the 'base expansion' of each i, plus the indexing to replace each
        digit with the corresponding item of Xs.

        Note that I've called the argument Xs, *NOT* list, because I want to use
        the built-in name 'list' for its normal purpose: making a new list
        that's a copy of an existing one (I prefer that to such goofy idioms as
        permutation[:], permutation+[], or permutation*1, even though they all
        do the same job and the last one is a real characters-saver; I think
        list(permutatio n) is more readable, personally).

        In general, it's best to avoid shadowing built-in names in a context
        where you're likely to want to use the original meaning of the names.

        Of course, there ARE other ways to count:

        def permute(Xs, N):
        permutations = []
        permutation = [0] * N
        X = len(Xs)
        while True:
        for k in xrange(N):
        permutation[k] += 1
        if permutation[k] < X: break
        permutation[k] = 0
        else:
        break
        permutations.ap pend([Xs[k] for k in permutation])
        return permutations

        Here, I need to use a list comprehension (or some slightly more obscure
        construct, such as map(Xs.__getite m__, permutation)... ) at append time,
        to construct the list of items of Xs from the list of indexes ("digits")
        which the algorithm naturally generates. I hold digits in a
        little-endian way, btw, simply because it's marginally simpler and you
        did not specify any particular ordering for the result.

        Each of these solutions can be enhanced by making it into a generator --
        remove the permutations=[] at the start, turn the permutations.ap pend
        call into a yield statement. However, you'll have to turn the simple
        "print permute(..." into a "print list(permute(.. ." in your testcase.
        No big deal, if you do need a list as a result; it's frequent that what
        you want is to LOOP over the result, in which case there's no need to
        make it into a list taking memory space all at once.

        Are these "counting" solutions any simpler than the recursive one? I
        don't really think so, for a cleanly-implemented recursion (one without
        globals and a first-time switch...!-) such as:

        def permute(Xs, N):
        if N <= 0:
        yield []
        return
        for x in Xs:
        for sub in permute(Xs, N-1):
        yield [x]+sub

        it seems to me this one has a strong simplicity advantage. However,
        considering the "counting" approaches is still instructive. Take the
        second one again: all the counting it does is a += 1 and a check if some
        limit is reached that way. But that's VERY close to the job of an
        iterator, so why not try to use an iterator directly instead of that
        "digit in base X"/"index into Xs" number...? Python generally prefers
        iterating directly over values, rather than over digits...

        def permute(Xs, N):
        state = [iter(Xs) for k in xrange(N)]
        permutation = [s.next() for s in state]
        while True:
        yield list(permutatio n)
        for k in xrange(N):
        try: permutation[k] = state[k].next()
        except StopIteration:
        state[k] = iter(Xs)
        permutation[k] = state[k].next()
        else:
        break
        else:
        break

        A bit cutesy, perhaps, particularly with the two "else: break" back to
        back (one for the try statement breaking out of the for, one for the for
        statement breaking out of the while).

        And so on, and so forth. There's hardly any limit to the fun one can
        have exploring such problems.

        But, for simplicity, recursion is still hard to beat;-).


        Alex

        Comment

        • Steve Goldman

          #5
          Re: Recursive function to develop permutations

          Thanks to everyone for lots to think about. I really like the concept of
          counting in base "X" as a model for this problem.

          But now I have another question. In the elegant recursive generator, below,
          the yield expression for the terminating condition is followed by a return
          statement. Why is that necessary? I know it is, because I tried taking it
          out with unhappy consequences. I thought yield had the same effect as
          return, except that the function's state was "frozen" until the next
          iteration.

          Thanks again.


          [color=blue]
          > Are these "counting" solutions any simpler than the recursive one? I
          > don't really think so, for a cleanly-implemented recursion (one without
          > globals and a first-time switch...!-) such as:
          >
          > def permute(Xs, N):
          > if N <= 0:
          > yield []
          > return
          > for x in Xs:
          > for sub in permute(Xs, N-1):
          > yield [x]+sub
          >[/color]



          Comment

          • Steven Bethard

            #6
            Re: Recursive function to develop permutations

            Steve Goldman <steve_g <at> ix.netcom.com> writes:
            [color=blue]
            > But now I have another question. In the elegant recursive generator, below,
            > the yield expression for the terminating condition is followed by a return
            > statement. Why is that necessary?[/color]
            [snip][color=blue][color=green]
            > > def permute(Xs, N):
            > > if N <= 0:
            > > yield []
            > > return
            > > for x in Xs:
            > > for sub in permute(Xs, N-1):
            > > yield [x]+sub[/color][/color]

            The 'return' statement tells the generator to exit the function (without
            saving the state. So,
            [color=blue][color=green][color=darkred]
            >>> def gen():[/color][/color][/color]
            .... yield 1
            .... return
            .... yield 2
            ....[color=blue][color=green][color=darkred]
            >>> list(gen())[/color][/color][/color]
            [1]

            Note that the second yield is never reached. So the source of your example
            above chose to use the 'return' statement instead of the perhaps more
            intuitive (but slightly more indented):

            def permute(Xs, N):
            if N <= 0:
            yield []
            else:
            for x in Xs:
            for sub in permute(Xs, N-1):
            yield [x]+sub

            Steve

            Comment

            • Alex Martelli

              #7
              Re: Recursive function to develop permutations

              Steve Goldman <steve_g@ix.net com.com> wrote:
              [color=blue]
              > Thanks to everyone for lots to think about. I really like the concept of
              > counting in base "X" as a model for this problem.[/color]

              You're welcome.
              [color=blue]
              > But now I have another question. In the elegant recursive generator, below,
              > the yield expression for the terminating condition is followed by a return
              > statement. Why is that necessary? I know it is, because I tried taking it[/color]

              It's not; a
              raise StopIteration
              or putting the rest of the program in an 'else:' would be just as good.
              [color=blue]
              > out with unhappy consequences. I thought yield had the same effect as
              > return, except that the function's state was "frozen" until the next
              > iteration.[/color]

              You need to terminate the recursion sooner or later. To do that, you
              must get a StopIteration exception raised. That happens with the
              appropriate raise statement, or a return, or "falling off the end of the
              code", which is equivalent to a return.

              I generally prefer return, as it's concise and avoids extra nesting.
              But if you'd rather do if/else, you can code, for example:

              def permute(Xs, N):
              if N > 0:
              for x in Xs:
              for sub in permute(Xs, N-1):
              yield [x]+sub
              else:
              yield []

              If you do choose an if/else it seems to me things are more readable when
              you arrange them this way (rather than with if N<=0, so the for in the
              else). Matter of tastes, of course.


              Alex

              Comment

              • Hung Jung Lu

                #8
                Re: Recursive function to develop permutations

                aleaxit@yahoo.c om (Alex Martelli) wrote:[color=blue]
                >
                > def permute(Xs, N):
                > if N > 0:
                > for x in Xs:
                > for sub in permute(Xs, N-1):
                > yield [x]+sub
                > else:
                > yield []
                >
                > If you do choose an if/else it seems to me things are more readable when
                > you arrange them this way (rather than with if N<=0, so the for in the
                > else). Matter of tastes, of course.[/color]

                For an unreadable voodoo version, here is a one-liner:

                permute = lambda Xs, N: reduce(lambda r, a: [p + [x] for p in r for x
                in Xs], range(N), [[]])

                print permute(['red', 'white', 'blue', 'green'], 3)

                Actually, we should probably not call it "permute". In combinatorics,
                "permutatio n" usually is reserved for sorting operation on a given
                combination. However, I don't have a good name for the described
                operation, either. Some people would probably call it "sequential draw
                with replacement". It's more like "combinatio n" as in the case of a
                "combinatio n lock", or as in "slot-machine combinations". (46 entries
                in Google. Zero entry for "slot-machine permutations", quoted search
                used.)

                For simple permutation on a given list, the voodoo version is:

                permutations = lambda x: reduce(lambda r, a: [p[:i] + [a] + p[i:] for
                p in r for i in range(len(p)+1)], x[1:], [x[:1]])

                print permutations(['a', 'b', 'c'])

                No if/else statements. No explicit recursion. And it works for
                zero-length lists as well. Now, if someone could do the general P(n,
                k) and C(n, k) versions, I'd appreciate it. :)

                Hung Jung

                Comment

                • Thorsten Kampe

                  #9
                  Re: Recursive function to develop permutations

                  * Steve Goldman (2004-10-19 02:34 +0200)[color=blue]
                  > I am trying to come up with a way to develop all n-length permutations of a
                  > given list of values.[/color]

                  I'm sorry but there are no "n-length permutations" in
                  mathematics/combinatorics. There are variations and combinations -
                  each of them with and without repetition. The difference is that with
                  combinations order of the elements is irrelevant and with variations
                  order is relevant.

                  Permutations always have the full length of the original list.
                  Permutations are divided in permutations with and without repetition
                  (like combinations and variations).

                  For an (unoptimized) version of permutations see:



                  Thorsten

                  PS If you are interested I can post the code for all six
                  possibilities. The code computes optionally the size of the result
                  list (because this grows big very soon) so you don't run out of memory
                  or time

                  Comment

                  • Thorsten Kampe

                    #10
                    Re: Recursive function to develop permutations

                    * Hung Jung Lu (2004-10-22 07:09 +0200)[color=blue]
                    > Actually, we should probably not call it "permute". In combinatorics,
                    > "permutatio n" usually is reserved for sorting operation on a given
                    > combination.[/color]

                    Not at all. Permutations and combinations have nothing in common. And
                    permutations aren't "sorted".

                    Comment

                    • Hung Jung Lu

                      #11
                      Re: Recursive function to develop permutations

                      hungjunglu@yaho o.com (Hung Jung Lu) wrote:[color=blue]
                      > No if/else statements. No explicit recursion. And it works for
                      > zero-length lists as well. Now, if someone could do the general P(n,
                      > k) and C(n, k) versions, I'd appreciate it. :)[/color]

                      Well, here they are: (for functional people, basically I used
                      tail-call and parameter list to emulate looping variables... standard
                      trick, nothing new. The formulas might be further simplified?)

                      permutations = lambda Xs, k: [p[0] for p in reduce(lambda r, i:
                      [[x+[y[i]], y[:i]+y[i+1:]] for (x, y) in r for i in range(len(y))],
                      range(k), [[[], Xs]])]

                      combinations = lambda Xs, k: [p[0] for p in reduce(lambda r, i:
                      [[x+[y[i]], y[i+1:]] for (x, y) in r for i in range(len(y))],
                      range(k), [[[], Xs]])]

                      slot_machine_co mbinations = lambda Xs, k: [p[0] for p in reduce(lambda
                      r, i: [[x+[y[i]], y] for (x, y) in r for i in range(len(y))],
                      range(k), [[[], Xs]])]

                      sample = ['a', 'b', 'c', 'd']
                      print permutations(sa mple, 2)
                      print combinations(sa mple, 2)
                      print slot_machine_co mbinations(samp le, 2)

                      The three formulas only differ in the treatment of the y[] lists in
                      looping. That part can be parametrized into separate lambdas if so
                      desired.

                      permutations: y --> y[:i] + y[i+1:]
                      combinations: y --> y[i+1:]
                      slot_machine_co mbinations: y --> y

                      Hung Jung

                      Comment

                      Working...