0

I am trying to find an algorithm that allows me to create all permutations/combinations of list elements, including the ones that occur when each of them are omitted.

For example, a list that contains

[a, b, c]

should produce the standard permutations

[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, b, a], [c, a, b]

but also these ones that result when one of the list entries is omitted

[a, b], [a, c], [b, a], [b, c], [c, a], [c, b]

and

[a], [b], [c]

Anyone have an idea for the best approach here? Unfortunately, Heap's algorithm only generates the standard permutations and I have not found any other algorithm that would give me the entire spectrum of permutations I need.

5
  • Software recommendations are off-topic for StackOverflow, but they are accepted at Software Recommendations. Commented Apr 12, 2022 at 23:41
  • Use heap's algorithm to get permutations of length n, create a list that is the original list - permutations of length 1 and use heap's to get permutations of length n-1, create a list that is the original list - permutations of length n-1 (so it's of length 2, use heap's on this list to get permutations of length 2 and so on Commented Apr 12, 2022 at 23:45
  • @Thomas Matthews I have no idea what you're talking about. I am not asking for any Software Recommendations. Commented Apr 13, 2022 at 0:10
  • @cSharp Yes, that is a solid approach, and I thought of brute-forcing it like that, but I was hoping there might actually an established algorithm out there that handles it intrinsically. Commented Apr 13, 2022 at 0:12
  • 1
    Given the list [a,b,c], there are 7 non-empty sublists that need to be permuted. The elements in those sublists can be selected by using the binary pattern of the numbers 1 thru 7. So just count from 1 to (2^n)-1, create a list by selecting elements per the binary pattern, and permute each list. Commented Apr 13, 2022 at 1:17

2 Answers 2

2

Here's a simple implementation of Heap's algorithm in Python, written as a generator. It might not be the presentation you're used to, since it uses a mid-tested loop, which was how it was initially presented by Robert Sedgewick, but it has exactly the same sequence of steps. (Unfortunately, Python doesn't have a mid-tested loop construct, so this version has a useless test at the beginning as well.)

def heap_perm(it):
    def helper(n):
        if n == 0:
            yield A[:]
        else:
            for i in range(n):
                yield from helper(n-1)
                if i < n - 1:
                    if n % 2:
                        A[0], A[n-1] = A[n-1], A[0]
                    else:
                        A[i], A[n-1] = A[n-1], A[i]

    A = list(it)
    yield from helper(len(A))

An important aspect of the order of permutations generated by this algorithm is that all the permutations with a particular suffix are output consecutively, regardless of the length of the suffix. So if we want all the suffixes as well, we just need to produce the suffix when we first encounter it, which is just before the recursion. If you put these two functions side by side, you'll see how similar they are.

def heap_subset_perm(it):
    def helper(n):
        for i in range(n):
            yield A[n-1:]
            yield from helper(n-1)
            if i < n - 1:
                if n % 2:
                    A[0], A[n-1] = A[n-1], A[0]
                else:
                    A[i], A[n-1] = A[n-1], A[i]

    A = list(it)
    yield from helper(len(A))

In the sample run, I right-justified the output so that the suffix property is more visible:

>>> for p in heap_subset_perm("abcd"):
...     print(f"{str(p):>20}")
... 
               ['d']
          ['c', 'd']
     ['b', 'c', 'd']
['a', 'b', 'c', 'd']
     ['a', 'c', 'd']
['b', 'a', 'c', 'd']
          ['b', 'd']
     ['a', 'b', 'd']
['c', 'a', 'b', 'd']
     ['c', 'b', 'd']
['a', 'c', 'b', 'd']
          ['a', 'd']
     ['c', 'a', 'd']
['b', 'c', 'a', 'd']
     ['b', 'a', 'd']
['c', 'b', 'a', 'd']
               ['c']
          ['a', 'c']
     ['b', 'a', 'c']
['d', 'b', 'a', 'c']
     ['d', 'a', 'c']
['b', 'd', 'a', 'c']
          ['b', 'c']
     ['d', 'b', 'c']
['a', 'd', 'b', 'c']
     ['a', 'b', 'c']
['d', 'a', 'b', 'c']
          ['d', 'c']
     ['a', 'd', 'c']
['b', 'a', 'd', 'c']
     ['b', 'd', 'c']
['a', 'b', 'd', 'c']
               ['b']
          ['d', 'b']
     ['c', 'd', 'b']
['a', 'c', 'd', 'b']
     ['a', 'd', 'b']
['c', 'a', 'd', 'b']
          ['c', 'b']
     ['a', 'c', 'b']
['d', 'a', 'c', 'b']
     ['d', 'c', 'b']
['a', 'd', 'c', 'b']
          ['a', 'b']
     ['d', 'a', 'b']
['c', 'd', 'a', 'b']
     ['c', 'a', 'b']
['d', 'c', 'a', 'b']
               ['a']
          ['b', 'a']
     ['c', 'b', 'a']
['d', 'c', 'b', 'a']
     ['d', 'b', 'a']
['c', 'd', 'b', 'a']
          ['c', 'a']
     ['d', 'c', 'a']
['b', 'd', 'c', 'a']
     ['b', 'c', 'a']
['d', 'b', 'c', 'a']
          ['d', 'a']
     ['b', 'd', 'a']
['c', 'b', 'd', 'a']
     ['c', 'd', 'a']
['b', 'c', 'd', 'a']

You could do the same thing with a permutation generator which produced permutations in prefix order, such as the lexicographically ordered generator implemented in itertools.

Sign up to request clarification or add additional context in comments.

1 Comment

Very cool! This is even neater and nicer than my implementation. Thank you so much!
1

I solved it by recursively calling Heap's algorithm, with each recursion containing a loop that removes one element, respectively, per iteration. This shrinks the list and iteratively removes each entry once per recursion, then permuting the leftovers.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.