在没有两个连续相等元素的情况下在python中重复排列

时间:2017-07-05 10:21:25

标签: 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 anything

不幸的是,itertools.permutation并不能满足我的需求(迭代中的每个元素在返回时都存在一次或没有时间)

我尝试了很多定义;首先,来自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)。

def crp2(iterable,r):
    a=list(range(0,r))
    s="\n"
    tab="    " #4 spaces
    l=[]
    for i in a:
        s+=(2*i*tab+"for a["+str(i)+"] in iterable:\n"+
        (2*i+1)*tab+"if "+str(i)+"==0 or a["+str(i)+"]!=a["+str(i-1)+"]:\n")
    s+=(2*i+2)*tab+"l.append(a.copy())"
    exec(s)
    return l

我知道,你不需要记住我:exec很丑,exec可能很危险,exec也不易读......我知道。 为了更好地理解这个功能,我建议你用exec替换exec(s)。

我举一个crp([0,1],2)的exec内部字符串的例子:

for a[0] in iterable:
    if 0==0 or a[0]!=a[-1]:
        for a[1] in iterable:
            if 1==0 or a[1]!=a[0]:
                l.append(a.copy())

但是,除了使用exec之外,我还需要更好的函数,因为crp2仍然太慢(即使比crp0更快);没有使用exec,有没有办法用r重新创建代码?还有其他方法可以做我需要的吗?

6 个答案:

答案 0 :(得分:2)

您可以尝试返回生成器而不是列表。使用较大的$this->get('mailer')->send($message);值时,您的方法将需要很长时间才能处理r并返回一个巨大的列表。

使用此变体,您应该非常快速地获得第一个元素:

product(iterable,repeat=r)

答案 1 :(得分:2)

您可以将序列准备两半,然后预处理后半部分以找到兼容的选择。

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快一百倍。

答案 2 :(得分:1)

您可以直接使用正确的元素生成列表,而不是过滤元素。此方法使用递归来创建笛卡尔积:

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)

答案 3 :(得分:1)

我同意@ EricDuminil的评论,即你不想要“重复排列”。您希望迭代产品的重要子集多次出现。我不知道最好用什么名字:我只称它们为产品。

这是一种构建每个产品系列而不构建所有产品然后过滤掉您想要的产品的方法。我的方法是主要使用迭代的索引而不是迭代本身 - 而不是所有索引,而忽略最后一个索引。因此,我没有直接使用[2, 3, 5, 7][0, 1, 2]合作。然后我使用这些指数的产品。我可以通过将每个索引与前一个索引进行比较来转换[1, 2, 2]其中r=3的产品。如果索引大于或等于前一个索引,则将当前索引增加1。这可以防止两个索引相等,这也可以回到使用所有索引。因此,[1, 2, 2]转换为[1, 2, 3],其中最终2已更改为3。我现在使用这些索引从iterable中选择适当的项,因此带有[2, 3, 5, 7]的可迭代r=3获取行[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

%timeit在我的Spyder / IPython配置中使用crp3([0,1], 3)8.54 µs per loop显示crp2([0,1], 3)133 µs per loop显示iterable。这显示了相当大的速度提升!我的例程在r很短且len ** r很大的情况下效果最佳 - 您的例程找到len行(其中len * (len-1) ** (r-1)是可迭代的长度)并在我的过程中过滤它们找到crp2()行而不进行过滤。

顺便说一下,您的if会进行过滤,如代码exec中的if行所示。我的代码中的唯一l不会过滤行,它会修改行中的项目。如果iterable中的项不是唯一的,我的代码会返回令人惊讶的结果:如果这是一个问题,只需将iterable更改为一个集以删除重复项。请注意,我将L名称替换为l:我认为1太容易与IL.append(a)混淆,应该避免。我的代码可以很容易地更改为生成器:将yield a替换为L = []并删除行return L.carousel{ display:flex!important;/* overrides previous values or set via js */ align-items:center;/* that's where centering happens and overflows on both sides */ }

答案 4 :(得分: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))) ]

答案 5 :(得分:0)

详细说明@ 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]

small是@ eric-duminil的答案的改编版,它使用了Python的著名的for...else循环

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
相关问题