Python 没有两个连续相等元素的重复排列
我需要一个函数,它生成所有置换,重复一个iterable,子句中两个连续的元素必须是不同的;比如说Python 没有两个连续相等元素的重复排列,python,algorithm,combinations,permutation,Python,Algorithm,Combinations,Permutation,我需要一个函数,它生成所有置换,重复一个iterable,子句中两个连续的元素必须是不同的;比如说 f([0,1],3).sort()==[(0,1,0),(1,0,1)] #or f([0,1],3).sort()==[[0,1,0],[1,0,1]] #I don't need the elements in the list to be sorted. #the elements of the return can be tuples or lists, it doesn't change
f([0,1],3).sort()==[(0,1,0),(1,0,1)]
#or
f([0,1],3).sort()==[[0,1,0],[1,0,1]]
#I don't need the elements in the list to be sorted.
#the elements of the return can be tuples or lists, it doesn't change anything
不幸的是,itertools.permutation不能满足我的需要(iterable中的每个元素在返回中都存在一次或没有出现过)
我试过很多定义;首先,从itertools.product(iterable,repeat=r)输入中筛选元素,但是对于我需要的东西来说太慢了
from itertools import product
def crp0(iterable,r):
l=[]
for f in product(iterable,repeat=r):
#print(f)
b=True
last=None #supposing no element of the iterable is None, which is fine for me
for element in f:
if element==last:
b=False
break
last=element
if b: l.append(f)
return l
其次,我尝试为循环构建r,一个在另一个内部(其中r是置换类,在数学中表示为k)
我知道,你没必要记得我:exec很难看,exec很危险,exec不容易阅读。。。我知道。
为了更好地理解函数,我建议您将exec替换为print
我举一个例子,说明crp([0,1],2)的exec中的字符串:
但是,除了使用exec,我还需要一个更好的函数,因为crp2仍然太慢(即使比crp0快);有没有办法在不使用exec的情况下用r重新创建代码?有没有别的方法可以满足我的需要 您可以尝试返回生成器而不是列表。对于较大的
r
,您的方法将花费很长时间来处理产品(iterable,repeat=r)
,并将返回一个巨大的列表
使用此变体,您应该很快获得第一个元素:
from itertools import product
def crp0(iterable, r):
for f in product(iterable, repeat=r):
last = f[0]
b = True
for element in f[1:]:
if element == last:
b = False
break
last = element
if b:
yield f
for no_repetition in crp0([0, 1, 2], 12):
print(no_repetition)
# (0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1)
# (1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0)
那么:
from itertools import product
result = [ x for x in product(iterable,repeat=r) if all(x[i-1] != x[i] for i in range(1,len(x))) ]
您可以将序列分成两半,然后对后半部分进行预处理以找到兼容的选项
def crp2(I,r):
r0=r//2
r1=r-r0
A=crp0(I,r0) # Prepare first half sequences
B=crp0(I,r1) # Prepare second half sequences
D = {} # Dictionary showing compatible second half sequences for each token
for i in I:
D[i] = [b for b in B if b[0]!=i]
return [a+b for a in A for b in D[a[-1]]]
在iterable=[0,1,2]和r=15的测试中,我发现这种方法比仅仅使用crp0快上百倍。您可以直接生成一个只包含正确元素的列表,而不是过滤元素。此方法使用递归创建笛卡尔积:
def product_no_repetition(iterable, r, last_element=None):
if r == 0:
return [[]]
else:
return [p + [x] for x in iterable
for p in product_no_repetition(iterable, r - 1, x)
if x != last_element]
for no_repetition in product_no_repetition([0, 1], 12):
print(no_repetition)
我同意@EricDuminil的评论,你不想要“重复排列”。你想要iterable的乘积的一个重要子集,它自身多次重复。我不知道什么名字最合适:我就叫它们“产品” 这里有一种方法,它构建每个产品线,而不构建所有产品,然后过滤掉您想要的产品。我的方法是主要处理iterable的索引,而不是iterable本身——不是所有的索引,而是忽略最后一个。因此,我不是直接使用
[2,3,5,7]
而是使用[0,1,2]
。然后我处理这些指数的乘积。我可以通过将每个索引与前一个索引进行比较来转换产品,例如[1,2,2]
,其中r=3
。如果一个索引大于或等于前一个,我将当前索引增加一。这可以防止两个索引相等,而且还可以重新使用所有索引。因此,[1,2,2]
被转换为[1,2,3]
,其中最后的2
被更改为3
。现在,我使用这些索引从iterable中选择适当的项,因此带有r=3
的iterable[2,3,5,7]
获得行[3,5,7]
。第一个索引的处理方式不同,因为它没有以前的索引。我的代码是:
from itertools import product
def crp3(iterable, r):
L = []
for k in range(len(iterable)):
for f in product(range(len(iterable)-1), repeat=r-1):
ndx = k
a = [iterable[ndx]]
for j in range(r-1):
ndx = f[j] if f[j] < ndx else f[j] + 1
a.append(iterable[ndx])
L.append(a)
return L
来自itertools导入产品的
def crp3(可调,r):
L=[]
对于范围内的k(len(iterable)):
对于产品中的f(范围(len(iterable)-1),重复=r-1):
ndx=k
a=[iterable[ndx]]
对于范围(r-1)内的j:
如果f[j]
在crp3([0,1],3)
上的我的Spyder/IPython配置中使用%timeit
,每个循环显示8.54µs
,而您的crp2([0,1],3)
显示每个循环133µs
。这表明速度有了相当大的提高!我的例程在iterable
短而r
大的地方工作得最好——你的例程会找到len**r
行(其中len
是iterable的长度)并对其进行过滤,而我的例程会在不进行过滤的情况下找到len*(len-1)**(r-1)
行
顺便说一下,您的
crp2()
会进行过滤,如代码中if
行所示,这是exec
ed。我的代码中唯一的if
不会过滤行,它会修改行中的项目。如果iterable中的项不是唯一的,我的代码确实会返回令人惊讶的结果:如果这是一个问题,只需将iterable更改为一个集合即可删除重复项。请注意,我将您的l
名称替换为l
:我认为l
太容易与1
或I
混淆,应该避免。我的代码可以很容易地更改为生成器:将L.append(a)
替换为yield a
,并删除L=[]
和return L
行 阐述@peter de rivaz的想法(分而治之)。将要创建的序列划分为两个子序列时,这些子序列相同或非常接近。如果r=2*k
为偶数,则将crp(k)
的结果存储在列表中,并将其与自身合并。如果r=2*k+1
,将crp(k)
的结果存储在一个列表中,并将其与自身和L
合并
def large(L, r):
if r <= 4: # do not end the divide: too slow
return small(L, r)
n = r//2
M = large(L, r//2)
if r%2 == 0:
return [x + y for x in M for y in M if x[-1] != y[0]]
else:
return [x + y + (e,) for x in M for y in M for e in L if x[-1] != y[0] and y[-1] != e]
一个小的基准:
print(timeit.timeit(lambda: crp2( [0, 1, 2], 10), number=1000))
#0.16290732200013736
print(timeit.timeit(lambda: crp2( [0, 1, 2, 3], 15), number=10))
#24.798989593000442
print(timeit.timeit(lambda: large( [0, 1, 2], 10), number=1000))
#0.0071403849997295765
print(timeit.timeit(lambda: large( [0, 1, 2, 3], 15), number=10))
#0.03471425700081454
哦,这个列表看起来真的,真的。。。谢谢,但重点不是一个更干净、更可读或更合成的函数:我所需要的只是一套更快的指令。我已经试过了,但它比之前提议的要慢(也比我的crp2慢)。谢谢!它比我的crp0快一点,但它仍然比crp2多占用大约30%的时间。我可以实现生成器而不是crp2中的列表,但它仍然太慢了(附言:当然,在执行死刑的时候,我会用passBy重新打印(无代表),顺便问一下,你想要什么
def large(L, r):
if r <= 4: # do not end the divide: too slow
return small(L, r)
n = r//2
M = large(L, r//2)
if r%2 == 0:
return [x + y for x in M for y in M if x[-1] != y[0]]
else:
return [x + y + (e,) for x in M for y in M for e in L if x[-1] != y[0] and y[-1] != e]
from itertools import product
def small(iterable, r):
for seq in product(iterable, repeat=r):
prev, *tail = seq
for e in tail:
if e == prev:
break
prev = e
else:
yield seq
print(timeit.timeit(lambda: crp2( [0, 1, 2], 10), number=1000))
#0.16290732200013736
print(timeit.timeit(lambda: crp2( [0, 1, 2, 3], 15), number=10))
#24.798989593000442
print(timeit.timeit(lambda: large( [0, 1, 2], 10), number=1000))
#0.0071403849997295765
print(timeit.timeit(lambda: large( [0, 1, 2, 3], 15), number=10))
#0.03471425700081454