【问题标题】:Permutations with repetition without two consecutive equal elements没有两个连续相等元素的重复排列
【发布时间】:2017-07-05 10:21:25
【问题描述】:

我需要一个函数,它通过重复一个可迭代的子句来生成所有排列,其中两个连续元素必须不同;例如

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(s) 替换为 print(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 重新创建代码?还有其他方法可以做我需要的吗?

【问题讨论】:

  • 顺便说一句,你想要的不是“重复排列”。
  • @EricDuminil 我知道,但最接近我需要的操作是结合重复
  • 是的,但与排列无关。
  • 正如我在回答中所说,这个问题大于排列(允许一些重复)但小于重复的产品(不允许一些重复)。我不知道什么是好名字,所以我只是称它们为产品。

标签: python algorithm combinations permutation


【解决方案1】:

您可以将序列分成两半,然后预处理另一半以找到兼容的选择。

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

【讨论】:

  • 我通过检查排序输出与 [0,1,2] 和 r=15 的 crp0 版本匹配来进行测试。您是否有一个您认为这会给出错误输出的示例?
  • 开枪,1000 道歉。我认为它是递归的,并没有注意到它使用crp0。我一定用修改后的crp0 对其进行了测试。我的错。
  • 没问题:(也有可能您使用此代码的第一个版本进行测试,这绝对是不正确的)
  • 谢谢。此时您的函数更快,替换 crp0 con crp2 (当然还有重命名函数)。
  • 如果r是偶数,你可以定义B=A
【解决方案2】:

您可以尝试返回生成器而不是列表。如果r 的值很大,您的方法将花费很长时间来处理product(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)

【讨论】:

  • 谢谢!它比我的 crp0 快一点,但它仍然比 crp2 多占用大约 30% 的时间。我可以在 crp2 中实现生成器而不是列表,但它仍然太慢了。 :(
  • ps:当然,当我误解执行时间时,我用 pass 重新放置了 print(no_rep)
【解决方案3】:

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

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)

【讨论】:

  • 生成正确的元素而不是过滤它们正是我编写 crp2 的原因。但是我没有找到没有exec的方法。谢谢,我去试试
【解决方案4】:

我同意@EricDuminil 的评论,即您不想要“重复排列”。您需要多次迭代的产品的重要子集。我不知道什么名字最好:我就叫他们产品吧。

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

在我的 Spyder/IPython 配置中使用 %timeit 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 行所示,即 execed。我的代码中唯一的if 不会过滤一行,它会修改行中的一个项目。如果迭代中的项目不是唯一的,我的代码确实会返回令人惊讶的结果:如果这是一个问题,只需将迭代更改为一个集合以删除重复项。请注意,我用L 替换了您的l 名称:我认为l 太容易与1I 混淆,应该避免使用。我的代码可以很容易地更改为生成器:将L.append(a) 替换为yield a 并删除L = []return L 行。

【讨论】:

    【解决方案5】:

    怎么样:

    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))) ]
    

    【讨论】:

    • 哦,这个列表理解看起来真的,真的...... meh
    • 谢谢,但重点不是更简洁、更易读或更综合的函数:我需要的只是一组更快的指令。我试过了,但它比之前提出的要慢(也比我的 crp2 慢)。
    【解决方案6】:

    详细阐述@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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-27
      • 2014-01-02
      • 2016-12-07
      • 1970-01-01
      • 2021-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多