找到`itertools` Python模块返回的给定组合(自然数)的索引

mmj*_*mmj 7 python indexing combinations python-itertools

鉴于k第一个n自然数的组合,由于某种原因,我需要在返回的那些中找到这种组合的位置itertools.combination(range(1,n),k)(原因是这样我可以使用a list而不是a dict来访问与每个组合相关联的值,知道组合).

由于itertools以常规模式产生组合,因此可以做到(并且我也找到了一个简洁的算法),但我正在寻找一种更快/更自然的方式,我可能会忽略它.

顺便说一句,这是我的解决方案:

def find_idx(comb,n):
    k=len(comb)
    idx=0
    last_c=0
    for c in comb:
        #idx+=sum(nck(n-2-x,k-1) for x in range(c-last_c-1)) # a little faster without nck caching
        idx+=nck(n-1,k)-nck(n-c+last_c,k) # more elegant (thanks to Ray), faster with nck caching
        n-=c-last_c
        k-=1
        last_c=c
    return idx
Run Code Online (Sandbox Code Playgroud)

其中nck返回二项式系数n,k.

例如:

comb=list(itertools.combinations(range(1,14),6))[654] #pick the 654th combination
find_idx(comb,14) # -> 654
Run Code Online (Sandbox Code Playgroud)

这是一个等价但可能不太复杂的版本(实际上我从下一个版本中导出了前一个版本).我将组合的整数c视为二进制数字中1的位置,我在解析0/1时构建了一个二叉树,我在解析过程中找到了一个常规的索引增量模式:

def find_idx(comb,n):
    k=len(comb)
    b=bin(sum(1<<(x-1) for x in comb))[2:]
    idx=0
    for s in b[::-1]:
        if s=='0':
            idx+=nck(n-2,k-1)
        else:
            k-=1
        n-=1
    return idx
Run Code Online (Sandbox Code Playgroud)

Ray*_*Ray 2

你的解决方案看起来相当快。在 中find_idx,您有两个 for 循环,可以使用以下公式优化内部循环:

C(n, k) + C(n-1, k) + ... + C(n-r, k) = C(n+1, k+1) - C(n-r, k+1)
Run Code Online (Sandbox Code Playgroud)

所以,你可以替换sum(nck(n-2-x,k-1) for x in range(c-last_c-1))nck(n-1, k) - nck(n-c+last_c, k).

我不知道你是如何实现你的nck(n, k)函数的,但它的时间复杂度应该是 O(k) 。这里我提供我的实现:

from operator import mul
from functools import reduce # In python 3
def nck_safe(n, k):
    if k < 0 or n < k: return 0
    return reduce(mul, range(n, n-k, -1), 1) // reduce(mul, range(1, k+1), 1)
Run Code Online (Sandbox Code Playgroud)

最后,你的解决方案变成 O(k^2),无需递归。它相当快,因为k​​不会太大。

更新

我注意到nck它的参数是(n, k). n和k都不会太大。我们可以通过缓存来加速程序。

def nck(n, k, _cache={}):
    if (n, k) in _cache: return _cache[n, k]
    ....
    # before returning the result
    _cache[n, k] = result
    return result
Run Code Online (Sandbox Code Playgroud)

在 python3 中,这可以通过使用装饰器来完成functools.lru_cache

@functools.lru_cache(maxsize=500)
def nck(n, k):
    ...
Run Code Online (Sandbox Code Playgroud)