Python 没有两个连续相等元素的重复排列

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

我需要一个函数,它生成所有置换,重复一个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 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