【问题标题】:Permutations with certain elements in specific index特定索引中某些元素的排列
【发布时间】:2020-05-02 01:01:34
【问题描述】:

是否可以找到列表 (n=27) 的所有排列,但元素 x0 到 x7 只能位于任何位置,只要它位于排列的索引 0 到 7 中?

keys = [x0, x1, x2, x3, x4, x5, x6, x7 ... x26]
[x1, x2, x3, x4, x5, x6, x7, x0 ... x26] #is okay
[x0, x1, x2, x3, x4, x5, x6, x8, x7 ... x26] #is NOT okay

我需要它能够从第 n 个排列中“恢复”,因为会有很多排列我无法一次测试所有排列。它可能必须是一个生成器(某种类型的),所以我可以在生成每个排列时对其进行测试,否则它会立即耗尽内存。

任何指针都非常感谢。

我考虑过的解决方案:

permitted = [x0, x1, x2, x3, x4, x5, x6, x7]

for p in itertools.permutations(keys):
   if p[0] not in permitted:
      continue
   if p[1] not in permitted:
      continue
   ...
   # if it passes all the limitations, test this permutation
   test(p)

问题在于我无法生成所有排列并在一次未解释的运行中测试它们。

我从这个答案here尝试的另一种方法:

from math import factorial

def ith_permutation(i, seq, r=None):
    li = list(seq)
    length = len(li)

    if r is None:
        r = length
    res = []
    current_factorial = factorial(length) // factorial(length - r)

    if current_factorial <= i:
        raise ValueError('out of range')

    for x in range(length, length-r, -1):
        current_factorial //= x
        div, mod = divmod(i, current_factorial)
        i = mod
        res.append(li[div])
        del(li[div])

    return res


for i in range(0, factorial(len(keys))-1):
   p = ith_permutation(i, keys)
   test(p)

这在原则上与上面相同,但我必须再次经历 1.08e+28 排列!这是不可能的。

【问题讨论】:

    标签: python algorithm permutation


    【解决方案1】:

    首先,您必须编写一个函数,该函数将为您提供列表中元素的第 n 个排列。然后,您可以将 0..7 子列表的排列与 8...26 子列表的排列组合起来。

    可以使用由阶乘组成的变量基来定义获得第 n 个排列的函数。例如,N 大小列表的第一个元素将位于 0*base, 1*base, 2*base, ... 所以您可以通过计算 base (N-1) 的值来确定第一个元素的索引)!并将位置除以该基数。该除法的其余部分是 N-1 个剩余元素中第二个元素的位置。您可以递归地执行此过程,直到到达最后一个元素。

    例如:

    from math import factorial
    
    def nthPermute(A,n):
        if not A: return tuple()        
        i,j = divmod(n,factorial(len(A)-1))
        return (A[i],)+nthPermute(A[:i]+A[i+1:],j)
    

    输出:

    for i in range(24):
        print(i,nthPermute("ABCD",i))
    
    0  ('A', 'B', 'C', 'D')
    1  ('A', 'B', 'D', 'C')
    2  ('A', 'C', 'B', 'D')
    3  ('A', 'C', 'D', 'B')
    4  ('A', 'D', 'B', 'C')
    5  ('A', 'D', 'C', 'B')
    6  ('B', 'A', 'C', 'D')
    7  ('B', 'A', 'D', 'C')
    8  ('B', 'C', 'A', 'D')
    9  ('B', 'C', 'D', 'A')
    10 ('B', 'D', 'A', 'C')
    11 ('B', 'D', 'C', 'A')
    12 ('C', 'A', 'B', 'D')
    13 ('C', 'A', 'D', 'B')
    14 ('C', 'B', 'A', 'D')
    15 ('C', 'B', 'D', 'A')
    16 ('C', 'D', 'A', 'B')
    17 ('C', 'D', 'B', 'A')
    18 ('D', 'A', 'B', 'C')
    19 ('D', 'A', 'C', 'B')
    20 ('D', 'B', 'A', 'C')
    21 ('D', 'B', 'C', 'A')
    22 ('D', 'C', 'A', 'B')
    23 ('D', 'C', 'B', 'A')
    

    排列的顺序遵循列表中元素的顺序。如果您的列表已排序,您将能够使用二进制搜索算法来查找给定排列的索引:

    def indexOfPermute(A,P):
        lo,hi = 0,factorial(len(A))-1
        while lo<=hi:
            mid = (lo+hi)//2
            p = nthPermute(A,mid)
            if   p<P: lo = mid+1
            elif p>P: hi = mid-1
            else: return mid
    
    i = indexOfPermute("ABCD",tuple('BCAD'))
    print(i)
    # 8
    

    将相同的原则应用于您的两部分排列,您可以创建一个函数来获取 27 个元素的约束排列的第 n 个值。

    def nthPerm_8_19(A,n):
        i,j = divmod(n,factorial(19))
        return nthPermute(A[:8],i)+nthPermute(A[8:],j)
    

    输出:

    A = "12345678ABCDEFGHIJKLMNOPQRS"
    for g in range(0,factorial(19)*7,factorial(19)):
        for i in range(g,g+4):
            print(i,"".join(nthPerm_8_19(A,i)))
    
    0                  12345678ABCDEFGHIJKLMNOPQRS
    1                  12345678ABCDEFGHIJKLMNOPQSR
    2                  12345678ABCDEFGHIJKLMNOPRQS
    3                  12345678ABCDEFGHIJKLMNOPRSQ
    121645100408832000 12345687ABCDEFGHIJKLMNOPQRS
    121645100408832001 12345687ABCDEFGHIJKLMNOPQSR
    121645100408832002 12345687ABCDEFGHIJKLMNOPRQS
    121645100408832003 12345687ABCDEFGHIJKLMNOPRSQ
    243290200817664000 12345768ABCDEFGHIJKLMNOPQRS
    243290200817664001 12345768ABCDEFGHIJKLMNOPQSR
    243290200817664002 12345768ABCDEFGHIJKLMNOPRQS
    243290200817664003 12345768ABCDEFGHIJKLMNOPRSQ
    364935301226496000 12345786ABCDEFGHIJKLMNOPQRS
    364935301226496001 12345786ABCDEFGHIJKLMNOPQSR
    364935301226496002 12345786ABCDEFGHIJKLMNOPRQS
    364935301226496003 12345786ABCDEFGHIJKLMNOPRSQ
    486580401635328000 12345867ABCDEFGHIJKLMNOPQRS
    486580401635328001 12345867ABCDEFGHIJKLMNOPQSR
    486580401635328002 12345867ABCDEFGHIJKLMNOPRQS
    486580401635328003 12345867ABCDEFGHIJKLMNOPRSQ
    608225502044160000 12345876ABCDEFGHIJKLMNOPQRS
    608225502044160001 12345876ABCDEFGHIJKLMNOPQSR
    608225502044160002 12345876ABCDEFGHIJKLMNOPRQS
    608225502044160003 12345876ABCDEFGHIJKLMNOPRSQ
    729870602452992000 12346578ABCDEFGHIJKLMNOPQRS
    729870602452992001 12346578ABCDEFGHIJKLMNOPQSR
    729870602452992002 12346578ABCDEFGHIJKLMNOPRQS
    729870602452992003 12346578ABCDEFGHIJKLMNOPRSQ
    

    有了这个,您可以使用 nthPerm_8_19() 函数,就好像您有一个包含所有 4,904,730,448,484,106,240,000 个元素排列的超长列表。

    要实现一个“可恢复”的过程,您只需要在虚拟排列列表中记录位置并在恢复时从那里继续。您还可以使用该位置来“分片”计算以进行并行处理。

    索引方案还允许您“跳过”一大块排列。例如,如果您想要跳过排列到位置 11 的下一个值,您可以通过添加基数的模补 (26-11) 来更新索引! :

     i    = 851515702861824002       
     s    = "".join(nthPerm_8_19(A,i))  # '12346587ABCDEFGHIJKLMNOPRQS'[11] = 'D'
    
     base = factorial(26-11)
     i   += base - i % base
     s    = "".join(nthPerm_8_19(A,i))  # '12346587ABCEDFGHIJKLMNOPQRS'[11] = 'E' 
    

    [编辑]

    进一步细分(回应评论):

    def nthPerm_8_10_9(A,n):
        i,j = divmod(n,factorial(10)*factorial(9))
        j,k = divmod(j,factorial(9))
        return nthPermute(A[:8],i) + nthPermute(A[8:18],j) + nthPermute(A[18:],k)
    

    这可以像这样直接推广到 nthPermute() 函数中:

    def nthPermute(A,n,chunks=None):
        if not A: return tuple()
        if chunks is None:
            if n>=factorial(len(A)): return None
            i,j = divmod(n,factorial(len(A)-1))
            return (A[i],)+nthPermute(A[:i]+A[i+1:],j)
        result = tuple()
        for size in reversed(chunks):
            base   = factorial(size)
            n,i    = divmod(n,base)
            A,a    = A[:-size],A[-size:]
            result = nthPermute(a,i) + result
        return result if n==0 else None
    

    以及在反向函数中获取排列的索引(如果元素在块内排序):

    def indexOfPermute(A,P,chunks=None):
        lo,hi = 0,1
        for c in chunks or [len(A)]: hi *= factorial(c)
        hi -= 1
        while lo<=hi:
            mid = (lo+hi)//2
            p = nthPermute(A,mid,chunks)
            if   p<P: lo = mid+1
            elif p>P: hi = mid-1
            else: return mid
    

    这将允许您随心所欲地使用分块:

    P = nthPermute(A,121645100408832000,[8,19])
    print("".join(P),indexOfPermute(A,P,[8,19]))
    
    # 12345687ABCDEFGHIJKLMNOPQRS 121645100408832000
    
    
    P = nthPermute(A,26547069911040000,[8,10,9])
    print("".join(P),indexOfPermute(A,P,[8,10,9]))
    # 51234678ABCDEFGHIJKLMNOPQRS 26547069911040000
    
    
    P = nthPermute(A,67722117120000,[6,6,9,6])
    print("".join(P),indexOfPermute(A,P,[6,6,9,6]))
    # 41235678ABCDEFGHIJKLMNOPQRS 67722117120000
    

    【讨论】:

    • 这对阿兰很有帮助。由于排列的总数仍然很大,nthPerm_8_19 函数将如何变化,使其成为前 8 个元素,然后是接下来的 10 个,最后是最后的 9 个?
    【解决方案2】:

    请注意,您正在寻找x0,...,x7 的排列,然后是x8,...,x26 的排列。所以,双循环就可以了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-11
      • 1970-01-01
      • 2011-12-22
      • 1970-01-01
      • 2022-01-02
      • 2016-04-19
      • 1970-01-01
      相关资源
      最近更新 更多