这与问题How to generate all permutations of a list in Python
有关如何生成符合以下条件的所有排列:如果两个排列彼此相反(即[1,2,3,4]和[4,3,2, 1]),它们被认为是平等的,只有其中一个应该是最终结果。
示例:
permutations_without_duplicates ([1,2,3])
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
我正在置换包含唯一整数的列表。
结果排列的数量会很高,所以我想尽可能使用Python的生成器。
编辑:如果可能的话,我不想将所有排列的列表存储到内存中。
答案 0 :(得分:11)
如果按字典顺序生成排列,那么您不需要存储任何内容来计算是否已经看到给定排列的反转。你只需要按字典顺序将它与反向进行比较 - 如果它更小则返回它,如果它更大则跳过它。
可能有一种更有效的方法,但这很简单,并且具有您需要的属性(可作为生成器实现,使用O(n)工作内存)。
答案 1 :(得分:9)
我对SilentGhost的提案有一个非常好的跟进 - 发表一个单独的答案,因为评论的边距太窄而无法包含代码: - )
itertools.permutations
内置(自2.6起)并且速度很快。我们只需要一个过滤条件,每个(perm,perm [:: - 1])都会接受其中一个。由于OP说项目总是不同的,我们可以比较任何2个元素:
for p in itertools.permutations(range(3)):
if p[0] < p[-1]:
print p
打印:
(0, 1, 2)
(0, 2, 1)
(1, 0, 2)
这是有效的,因为反转排列总会翻转关系!
p[0] < p[1]
或任何其他对也可以使用,因此您也可以控制您获得的一半排列。
我不确定是否有更有效的过滤方法。 itertools.permutations
保证字典顺序,但字典位置p
和p[::-1]
以相当复杂的方式相关。特别是,只是停在中间是行不通的。
但我怀疑(没检查)具有2:1过滤的内置迭代器将胜过任何自定义实现。当然,它在简单性上获胜!
答案 2 :(得分:3)
编辑:完全更改以将所有内容保留为生成器(从不在内存中的整个列表)。应满足要求(仅计算一半可能的排列(不是反向排列)。 EDIT2 :从here添加了更短(且更简单)的阶乘函数。
EDIT3: :(见评论) - 可以在bwopah's version中找到包含改进的版本。
def fac(x):
return (1 if x==0 else x * fac(x-1))
def all_permutations(plist):
global counter
if len(plist) <=1:
yield plist
else:
for perm in all_permutations(plist[1:]):
for i in xrange(len(perm)+1):
if len(perm[:i] + plist[0:1] + perm[i:]) == lenplist:
if counter == limit:
raise StopIteration
else:
counter = counter + 1
yield perm[:i] + plist[0:1] + perm[i:]
counter = 0
plist = ['a','b','c']
lenplist = len(plist)
limit = fac(lenplist) / 2
all_permutations_gen = all_permutations(plist)
print all_permutations_gen
print list(all_permutations_gen)
答案 3 :(得分:3)
这是ChristopheD接受的答案的更简洁和更快的版本,我非常喜欢。递归很棒。我通过删除重复项使其强制执行传入列表的单一序列,但是它可能只是引发异常。
def fac(x):
return (1 if x==0 else x * fac(x-1))
def permz(plist):
plist = sorted(set(plist))
plen = len(plist)
limit = fac(plen) / 2
counter = 0
if plen==1:
yield plist
else:
for perm in permz(plist[1:]):
for i in xrange(plen):
if counter == limit:
raise StopIteration
counter += 1
yield perm[:i] + plist[0:1] + perm[i:]
# ---- testing ----
plists = [
list('love'),
range(5),
[1,4,2,3,9],
['a',2,2.1],
range(8)]
for plist in plists:
perms = list(permz(plist))
print plist, True in [(list(reversed(i)) in foo) for i in perms]
答案 4 :(得分:2)
这个怎么样:
from itertools import permutations
def rev_generator(plist):
reversed_elements = set()
for i in permutations(plist):
if i not in reversed_elements:
reversed_i = tuple(reversed(i))
reversed_elements.add(reversed_i)
yield i
>>> list(rev_generator([1,2,3]))
[(1, 2, 3), (1, 3, 2), (2, 1, 3)]
此外,如果返回值必须是一个列表,您只需将yield i更改为yield(i),但出于迭代目的,元组将正常工作。
答案 5 :(得分:2)
这是执行技巧的代码。为了摆脱重复,我注意到,对于你的列表,如果第一个位置的值大于最后一个位置的值,那么它将是一个dup。我创建了一个地图,以跟踪每个项目在列表中的位置,然后使用该地图进行测试。该代码也不使用递归,因此它的内存占用量很小。此外,列表可以是任何类型的项目,而不仅仅是数字,请参阅最后两个测试用例。
这是代码。
class Permutation:
def __init__(self, justalist):
self._data = justalist[:]
self._len=len(self._data)
self._s=[]
self._nfact=1
self._map ={}
i=0
for elem in self._data:
self._s.append(elem)
self._map[str(elem)]=i
i+=1
self._nfact*=i
if i != 0:
self._nfact2=self._nfact//i
def __iter__(self):
for k in range(self._nfact):
for i in range(self._len):
self._s[i]=self._data[i]
s=self._s
factorial=self._nfact2
for i in range(self._len-1):
tempi = (k // factorial) % (self._len - i)
temp = s[i + tempi]
for j in range(i + tempi,i,-1):
s[j] = s[j-1]
s[i] = temp
factorial //= (self._len - (i + 1))
if self._map[str(s[0])] < self._map[str(s[-1])]:
yield s
s=[1,2]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
s=[1,2,3]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
s=[1,2,3,4]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
s=[3,2,1]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
s=["Apple","Pear","Orange"]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
s=[[1,4,5],"Pear",(1,6,9),Permutation([])]
print("_"*25)
print("input list:",s)
for sx in Permutation(s):
print(sx)
这是我的测试用例的输出。
_________________________
input list: [1, 2]
[1, 2]
_________________________
input list: [1, 2, 3]
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
_________________________
input list: [1, 2, 3, 4]
[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, 4, 1, 3]
[3, 1, 2, 4]
[3, 2, 1, 4]
_________________________
input list: [3, 2, 1]
[3, 2, 1]
[3, 1, 2]
[2, 3, 1]
_________________________
input list: ['Apple', 'Pear', 'Orange']
['Apple', 'Pear', 'Orange']
['Apple', 'Orange', 'Pear']
['Pear', 'Apple', 'Orange']
_________________________
input list: [[1, 4, 5], 'Pear', (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
[[1, 4, 5], 'Pear', (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
[[1, 4, 5], 'Pear', <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9)]
[[1, 4, 5], (1, 6, 9), 'Pear', <__main__.Permutation object at 0x0142DEF0>]
[[1, 4, 5], (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>, 'Pear']
[[1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, 'Pear', (1, 6, 9)]
[[1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9), 'Pear']
['Pear', [1, 4, 5], (1, 6, 9), <__main__.Permutation object at 0x0142DEF0>]
['Pear', [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>, (1, 6, 9)]
['Pear', (1, 6, 9), [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>]
['Pear', <__main__.Permutation object at 0x0142DEF0>, [1, 4, 5], (1, 6, 9)]
[(1, 6, 9), [1, 4, 5], 'Pear', <__main__.Permutation object at 0x0142DEF0>]
[(1, 6, 9), 'Pear', [1, 4, 5], <__main__.Permutation object at 0x0142DEF0>]
答案 6 :(得分:2)
这是我的实施:
a = [1,2,3,4]
def p(l):
if len(l) <= 1:
yield l
else:
for i in range(len(l)):
for q in p([l[j] for j in range(len(l)) if j != i]):
yield [l[i]] + q
out = (i for i in p(a) if i < i[::-1])
P函数是一个常规的permu函数,可以产生所有可能性。迭代结果时完成过滤器。简单地说,它有两种可能的结果,即所有肌腱的较小一半和肌腱的较大一半。在此示例中,out包含列表的较小部分。
答案 7 :(得分:1)
这是onebyone建议的实现
来自http://en.wikipedia.org/wiki/Permutation#Lexicographical_order_generation的以下算法在给定置换后按字典顺序生成下一个置换。它就地改变了给定的排列。
功能:
def perms(items):
items.sort()
yield items[:]
m = [len(items)-2] # step 1
while m:
i = m[-1]
j = [ j for j in range(i+1,len(items)) if items[j]>items[i] ][-1] # step 2
items[i], items[j] = items[j], items[i] # step 3
items[i+1:] = list(reversed(items[i+1:])) # step 4
if items<list(reversed(items)):
yield items[:]
m = [ i for i in range(len(items)-1) if items[i]<items[i+1] ] # step 1
检查我们的工作:
>>> foo=list(perms([1,3,2,4,5]))
>>> True in [(list(reversed(i)) in foo) for i in foo]
False
答案 8 :(得分:1)
首先是一些设置代码:
try:
from itertools import permutations
except ImportError:
# straight from http://docs.python.org/library/itertools.html#itertools.permutations
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
def non_reversed_permutations(iterable):
"Return non-reversed permutations for an iterable with unique items"
for permutation in permutations(iterable):
if permutation[0] < permutation[-1]:
yield permutation
答案 9 :(得分:-2)
itertools.permutations
完全符合您的要求。您也可以使用reversed
built-in