If there are duplicates (meaning our set is a multi-set), you can save considerable time and effort by avoiding identical permutations. For example, there are only ten distinct permutations of {1,1,2,2,2}, instead of 120.
The challenge is to avoid duplicates by using backtracking and generating the permutations in lexicographic order.
Here is what I have tried so far but it's inefficient because I am still enumerating repeat elements. I am also sorting the input first.
def multiset_permutation(A):
def solve_permutation(k, seen):
# goal
if k == len(A) - 1 and tuple(A) not in seen:
seen.add(tuple(A))
result.append(A.copy())
for i in range(k, len(A)):
# make choice: swap elements
A[k], A[i] = A[i], A[k]
# explore: Generate all permutations for A[i + 1:]
solve_permutation(k + 1, seen)
# backtrack: undo choice
A[k], A[i] = A[i], A[k]
result = list()
seen = set()
solve_permutation(0, seen)
return result
>>> multiset_permutation(sorted(A))
[1, 1, 2], [1, 2, 1], [2, 1, 1]
What I am looking for is a way to break off the enumeration at the earliest chance. For example, given A=[1,1,2]
, the recursion tree would look like so with enumeration stopping at break-off points:
[]
/ | \
[1] [1](break off - already enumerated) [2]
/ \ / \ / \
[1,1] [1,2] [1,1] [1,2] [2,1] [2,1] (break off - already enumerated)
/ \ / \ / \
[1, 1, 2] [1, 2, 1] [1, 1, 2] [1, 2, 1] [2, 1, 1] [2, 1, 1]
Expected output:
[1, 1, 2], [1, 2, 1], [2, 1, 1]