生成具有重复元素的列表排列
Posted
技术标签:
【中文标题】生成具有重复元素的列表排列【英文标题】:Generate permutations of list with repeated elements 【发布时间】:2011-05-14 02:27:25 【问题描述】:在 Python 中,使用 itertools
模块生成列表的所有排列非常简单。我有一种情况,我使用的序列只有两个字符(即'1122'
)。我想生成所有独特的排列。
对于字符串 '1122'
,有 6 个唯一排列(1122
、1212
、1221
等),但 itertools.permutations
将产生 24 个项目。只记录唯一的排列很简单,但由于考虑到所有 720 个项目,收集它们需要的时间比必要的要长得多。
是否有一个函数或模块可以在生成排列时考虑重复元素,这样我就不必自己编写了?
【问题讨论】:
【参考方案1】:This web page 看起来很有希望。
def next_permutation(seq, pred=cmp):
"""Like C++ std::next_permutation() but implemented as
generator. Yields copies of seq."""
def reverse(seq, start, end):
# seq = seq[:start] + reversed(seq[start:end]) + \
# seq[end:]
end -= 1
if end <= start:
return
while True:
seq[start], seq[end] = seq[end], seq[start]
if start == end or start+1 == end:
return
start += 1
end -= 1
if not seq:
raise StopIteration
try:
seq[0]
except TypeError:
raise TypeError("seq must allow random access.")
first = 0
last = len(seq)
seq = seq[:]
# Yield input sequence as the STL version is often
# used inside do while.
yield seq[:]
if last == 1:
raise StopIteration
while True:
next = last - 1
while True:
# Step 1.
next1 = next
next -= 1
if pred(seq[next], seq[next1]) < 0:
# Step 2.
mid = last - 1
while not (pred(seq[next], seq[mid]) < 0):
mid -= 1
seq[next], seq[mid] = seq[mid], seq[next]
# Step 3.
reverse(seq, next1, last)
# Change to yield references to get rid of
# (at worst) |seq|! copy operations.
yield seq[:]
break
if next == first:
raise StopIteration
raise StopIteration
>>> for p in next_permutation([int(c) for c in "111222"]):
... print p
...
[1, 1, 1, 2, 2, 2]
[1, 1, 2, 1, 2, 2]
[1, 1, 2, 2, 1, 2]
[1, 1, 2, 2, 2, 1]
[1, 2, 1, 1, 2, 2]
[1, 2, 1, 2, 1, 2]
[1, 2, 1, 2, 2, 1]
[1, 2, 2, 1, 1, 2]
[1, 2, 2, 1, 2, 1]
[1, 2, 2, 2, 1, 1]
[2, 1, 1, 1, 2, 2]
[2, 1, 1, 2, 1, 2]
[2, 1, 1, 2, 2, 1]
[2, 1, 2, 1, 1, 2]
[2, 1, 2, 1, 2, 1]
[2, 1, 2, 2, 1, 1]
[2, 2, 1, 1, 1, 2]
[2, 2, 1, 1, 2, 1]
[2, 2, 1, 2, 1, 1]
[2, 2, 2, 1, 1, 1]
>>>
2017-08-12
七年后,这里有一个更好的算法(为了清晰起见):
from itertools import permutations
def unique_perms(series):
return "".join(p) for p in permutations(series)
print(sorted(unique_perms('1122')))
【讨论】:
每次迭代都使用reverse
可以吗?它具有O(n)
的复杂性,而且它在每次迭代中运行的事实可能会使整个算法变得非常缓慢。
我稍微修改了代码,使其更切中要害,并反映 Knuth 书中用于描述算法的名称。对于那些需要它的人,我发布链接:ideone.com/juG94
另外,我使用更多功能风格的编码(使用生成器)重写了这个算法。它比直接使用索引的版本慢几倍。尽管如此,算法的主要部分(以while True:
开头)在此版本中看起来更加清晰。代码在这里:ideone.com/mvu1y
干得好,但我不明白为什么它不适用于序列中的零
@freude:这个函数迭代直到它达到最大的词典排列,然后停止。因此,要获得所有排列,请确保对您的输入进行排序(使其成为最低排列,按字典顺序排列)。【参考方案2】:
More Itertools 有一个功能:
more-itertools.distinct_permutations(iterable)
产生iterable中元素的连续不同排列。
等价于
set(permutations(iterable))
,除了不重复 生成并丢弃。对于较大的输入序列,这是很多 更高效。
from more_itertools import distinct_permutations
for p in distinct_permutations('1122'):
print(''.join(p))
# 2211
# 2121
# 1221
# 2112
# 1212
# 1122
安装:
pip install more-itertools
【讨论】:
无疑是最简洁的答案【参考方案3】:使用 set 使解决方案更简单。具有重复字符的字符串,以及 不重复, 用作输入。
from itertools import permutations
def perm(s):
return set(permutations(s))
l = '1122'
perm(l)
('1', '1', '2', '2'),
('1', '2', '1', '2'),
('1', '2', '2', '1'),
('2', '1', '1', '2'),
('2', '1', '2', '1'),
('2', '2', '1', '1')
l2 = '1234'
perm(l2)
('1', '2', '3', '4'),
('1', '2', '4', '3'),
('1', '3', '2', '4'),
('1', '3', '4', '2'),
('1', '4', '2', '3'),
('1', '4', '3', '2'),
('2', '1', '3', '4'),
('2', '1', '4', '3'),
('2', '3', '1', '4'),
('2', '3', '4', '1'),
('2', '4', '1', '3'),
('2', '4', '3', '1'),
('3', '1', '2', '4'),
('3', '1', '4', '2'),
('3', '2', '1', '4'),
('3', '2', '4', '1'),
('3', '4', '1', '2'),
('3', '4', '2', '1'),
('4', '1', '2', '3'),
('4', '1', '3', '2'),
('4', '2', '1', '3'),
('4', '2', '3', '1'),
('4', '3', '1', '2'),
('4', '3', '2', '1')
【讨论】:
以下内容简洁明了:set(permutations(s)) @LelandHepworth,是的,你是对的。不需要使用 re 或 list。【参考方案4】:这也是一个常见的面试问题。在事件标准库modules 无法使用时,这里有一个需要考虑的实现:
我们定义一个lexicographic ordering of permutations。一旦我们这样做 我们可以从 smallest 排列和增量开始 直到我们达到最大排列。
def next_permutation_helper(perm):
if not perm:
return perm
n = len(perm)
"""
Find k such that p[k] < p[k + l] and entries after index k appear in
decreasing order.
"""
for i in range(n - 1, -1, -1):
if not perm[i - 1] >= perm[i]:
break
# k refers to the inversion point
k = i - 1
# Permutation is already the max it can be
if k == -1:
return []
"""
Find the smallest p[l] such that p[l] > p[k]
(such an l must exist since p[k] < p[k + 1].
Swap p[l] and p[k]
"""
for i in range(n - 1, k, -1):
if not perm[k] >= perm[i]:
perm[i], perm[k] = perm[k], perm[i]
break
# Reverse the sequence after position k.
perm[k + 1 :] = reversed(perm[k + 1 :])
return perm
def multiset_permutation(A):
"""
We sort array first and `next_permutation()` will ensure we generate
permutations in lexicographic order
"""
A = sorted(A)
result = list()
while True:
result.append(A.copy())
A = next_permutation_helper(A)
if not A:
break
return result
输出:
>>> multiset_permutation([1, 1, 2, 2])
[[1, 1, 2, 2], [1, 2, 1, 2], [1, 2, 2, 1], [2, 1, 1, 2], [2, 1, 2, 1], [2, 2, 1, 1]]
您可以在这一行使用join 将可变列表的输出转换为字符串:
result.append("".join(map(str, A.copy())))
得到:
['1122', '1212', '1221', '2112', '2121', '2211']
【讨论】:
【参考方案5】:from more_itertools import distinct_permutations
x = [p for p in distinct_permutations(['M','I','S', 'S', 'I'])]
for item in x:
print(item)
输出:
('I', 'S', 'S', 'I', 'M')
('S', 'I', 'S', 'I', 'M')
('S', 'S', 'I', 'I', 'M')
('I', 'S', 'I', 'S', 'M')
('S', 'I', 'I', 'S', 'M')
('I', 'I', 'S', 'S', 'M')
('I', 'S', 'I', 'M', 'S')
('S', 'I', 'I', 'M', 'S')
('I', 'I', 'S', 'M', 'S')
('I', 'I', 'M', 'S', 'S')
('I', 'S', 'S', 'M', 'I')
('S', 'I', 'S', 'M', 'I')
('S', 'S', 'I', 'M', 'I')
('S', 'S', 'M', 'I', 'I')
('I', 'S', 'M', 'S', 'I')
('S', 'I', 'M', 'S', 'I')
('S', 'M', 'I', 'S', 'I')
('S', 'M', 'S', 'I', 'I')
('I', 'M', 'S', 'S', 'I')
('M', 'I', 'S', 'S', 'I')
('M', 'S', 'I', 'S', 'I')
('M', 'S', 'S', 'I', 'I')
('I', 'S', 'M', 'I', 'S')
('S', 'I', 'M', 'I', 'S')
('S', 'M', 'I', 'I', 'S')
('I', 'M', 'S', 'I', 'S')
('M', 'I', 'S', 'I', 'S')
('M', 'S', 'I', 'I', 'S')
('I', 'M', 'I', 'S', 'S')
('M', 'I', 'I', 'S', 'S')
【讨论】:
【参考方案6】:一个非常简单的解决方案,可能类似于more_itertools
使用的解决方案,它利用@Brayoni 建议的排列的字典顺序,可以通过构建可迭代的索引来完成。
假设你有L = '1122'
。您可以使用以下内容构建一个非常简单的索引:
index = x: i for i, x in enumerate(sorted(L))
假设您有一个排列 P
和 L
。 P
有多少元素并不重要。字典顺序规定,如果您将P
映射到使用索引,它必须始终增加。像这样映射P
:
mapped = tuple(index[e] for e in p) # or tuple(map(index.__getitem__, p))
现在您可以丢弃小于或等于目前看到的最大值的元素:
def perm_with_dupes(it, n=None):
it = tuple(it) # permutations will do this anyway
if n is None:
n = len(it)
index = x: i for i, x in enumerate(it)
maximum = (-1,) * (len(it) if n is None else n)
for perm in permutations(it, n):
key = tuple(index[e] for e in perm)
if key <= maximum: continue
maximum = key
yield perm
请注意,除了保留最后一个最大项之外,没有额外的内存开销。如果您愿意,可以使用''
加入元组。
【讨论】:
以上是关于生成具有重复元素的列表排列的主要内容,如果未能解决你的问题,请参考以下文章