Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/364.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在“itertools”Python模块返回的组合中查找给定组合(自然数)的索引_Python_Indexing_Combinations_Itertools - Fatal编程技术网

在“itertools”Python模块返回的组合中查找给定组合(自然数)的索引

在“itertools”Python模块返回的组合中查找给定组合(自然数)的索引,python,indexing,combinations,itertools,Python,Indexing,Combinations,Itertools,给定第一个n自然数的k组合,出于某种原因,我需要在itertools返回的组合中找到该组合的位置。组合(范围(1,n),k)(原因是通过这种方式,我可以使用列表而不是dict来访问与每个组合相关的值,知道组合) 由于itertools以规则模式生成其组合,因此可以这样做(我还发现了一个简洁的算法),但我正在寻找一种更快/更自然的方法,我可能会忽略它 顺便说一下,这是我的解决方案: def find_idx(comb,n): k=len(comb) idx=0 last_c

给定第一个
n
自然数的
k
组合,出于某种原因,我需要在
itertools返回的组合中找到该组合的位置。组合(范围(1,n),k)
(原因是通过这种方式,我可以使用
列表
而不是
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
其中
nck
返回

例如:

comb=list(itertools.combinations(range(1,14),6))[654] #pick the 654th combination
find_idx(comb,14) # -> 654
这是一个等效但可能不太复杂的版本(实际上我从下一个版本中导出了上一个版本)。我将组合
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
def find_idx(梳,n):
k=透镜(梳状)

b=bin(sum)(1看起来你需要更好地指定你的任务,否则我只是弄错了。对我来说,当你迭代
itertools.composition
时,你可以将你需要的索引保存到一个适当的数据结构中。如果你需要所有的索引,那么我会使用
dict
(一个
dict
满足您的所有需求):


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

C(n, k) + C(n-1, k) + ... + C(n-r, k) = C(n+1, k+1) - C(n-r, k+1)
因此,您可以用
nck(n-c+last\u c,k)替换范围(c-last\u c-1)内x的
sum(nck(n-2-x,k-1))

我不知道你是如何实现你的
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)
在python3中,这可以通过使用
functools.lru\u cache
decorator完成:

@functools.lru_cache(maxsize=500)
def nck(n, k):
    ...
我找到了一些旧代码(尽管它已转换为Python 3语法),其中包含函数
composition\u index
,可以满足您的要求:

def fact(n, _f=[1, 1, 2, 6, 24, 120, 720]):
    """Return n!
    The “hidden” list _f acts as a cache"""
    try:
        return _f[n]
    except IndexError:
        while len(_f) <= n:
            _f.append(_f[-1] * len(_f))
        return _f[n]

def indexed_combination(n: int, k: int, index: int) -> tuple:
    """Select the 'index'th combination of k over n
    Result is a tuple (i | i∈{0…n-1}) of length k

    Note that if index ≥ binomial_coefficient(n,k)
    then the result is almost always invalid"""

    result= []
    for item, n in enumerate(range(n, -1, -1)):
        pivot= fact(n-1)//fact(k-1)//fact(n-k)
        if index < pivot:
            result.append(item)
            k-= 1
            if k <= 0: break
        else:
            index-= pivot
    return tuple(result)

def combination_index(combination: tuple, n: int) -> int:
    """Return the index of combination (length == k)

    The combination argument should be a sorted sequence (i | i∈{0…n-1})"""

    k= len(combination)
    index= 0
    item_in_check= 0
    n-= 1 # to simplify subsequent calculations
    for offset, item in enumerate(combination, 1):
        while item_in_check < item:
            index+= fact(n-item_in_check)//fact(k-offset)//fact(n+offset-item_in_check-k)
            item_in_check+= 1
        item_in_check+= 1
    return index

def test():
    for n in range(1, 11):
        for k in range(1, n+1):
            max_index= fact(n)//fact(k)//fact(n-k)
            for i in range(max_index):
                comb= indexed_combination(n, k, i)
                i2= combination_index(comb, n)
                if i2 != i:
                    raise RuntimeError("mismatching n:%d k:%d i:%d≠%d" % (n, k, i, i2))
def事实(n,_f=[1,1,2,6,24120720]):
“返回n!
“隐藏”列表用作缓存“”
尝试:
返回_f[n]
除索引器外:
而len(_f)元组:
“”“选择k对n的第个“索引”组合
结果是一个元组(i | i)∈长度为k的{0…n-1})
请注意,如果索引≥ 二项式_系数(n,k)
那么结果几乎总是无效的
结果=[]
对于项,枚举中的n(范围(n,-1,-1)):
pivot=fact(n-1)//fact(k-1)//fact(n-k)
如果索引<枢轴:
结果。追加(项)
k-=1
如果k int:
“”“返回组合的索引(长度==k)
组合参数应该是一个排序序列(i | i∈“{0…n-1}”
k=透镜(组合)
索引=0
检查中的项目=0
n-=1#以简化后续计算
对于抵销,枚举中的项目(组合,1):
当项目检查中的项目<项目:
索引+=事实(n-项检查中的项目)//事实(k-偏移)//事实(n+偏移-项检查中的项目)
检查中的项目+=1
检查中的项目+=1
回报指数
def test():
对于范围(1,11)内的n:
对于范围(1,n+1)内的k:
max_index=fact(n)//fact(k)//fact(n-k)
对于范围内的i(最大索引):
comb=索引_组合(n,k,i)
i2=组合指数(梳,n)
如果i2!=i:
引发运行时错误(“不匹配的n:%d k:%d i:%d≠%d“%(n,k,i,i2))
索引\u组合
执行反向操作


PS我记得我曾经尝试删除所有那些
fact
调用(通过替换适当的增量乘法和除法)但是代码变得更加复杂,实际上并没有更快。如果我用预先计算的阶乘列表代替
fact
函数,可以实现加速,但是对于我的用例来说,速度差异可以忽略不计,所以我保留了这个版本。

+1看看“源代码”可能会有所帮助
itertools.compositions
:这些看起来像是我保存在工具箱中的ranker的迭代版本。
find_idx
似乎比
unchoose
快,这并不奇怪,因为Python递归速度往往很慢。大声思考:这会有帮助吗?@Iarsmans很棒的链接,谢谢。不过我发现了le和
之间的区别维基百科和itertools使用的顺序:前者假设所选项目的顺序减少,后者则增加顺序。我正试图将我的算法简化为类似的东西(还不远)对于维基百科中优雅而快速的一个,我不知道它是否可行,因为这样的差异。使用
dict
比使用
list
更耗时、更消耗内存。此外,如果你有(像我一样)数十亿个组合,要存储所有梳/索引对,可能需要比现有内存和时间更多的内存和时间,而函数需要几个字节的内存,只有在需要时才花时间获取索引。很棒的“理论”改进,我一直在寻找这样一个公式。不幸的是,它似乎没有带来太多的性能提升。我尝试了一些测试,非常失望地发现,与原始版本相比,使用
sum
,速度没有提高。我想毕竟,计算2
nck
需要比计算更多的周期原始版本中的
sum
的几个
nck
。顺便说一句,我更新了代码以反映您的“理论”优化。下一个优化步骤应该是找到一个类似的公式,即使是主循环也是如此。遗憾的是,这个技巧没有很好地发挥作用。我在upd中引入了另一种方法
@functools.lru_cache(maxsize=500)
def nck(n, k):
    ...
def fact(n, _f=[1, 1, 2, 6, 24, 120, 720]):
    """Return n!
    The “hidden” list _f acts as a cache"""
    try:
        return _f[n]
    except IndexError:
        while len(_f) <= n:
            _f.append(_f[-1] * len(_f))
        return _f[n]

def indexed_combination(n: int, k: int, index: int) -> tuple:
    """Select the 'index'th combination of k over n
    Result is a tuple (i | i∈{0…n-1}) of length k

    Note that if index ≥ binomial_coefficient(n,k)
    then the result is almost always invalid"""

    result= []
    for item, n in enumerate(range(n, -1, -1)):
        pivot= fact(n-1)//fact(k-1)//fact(n-k)
        if index < pivot:
            result.append(item)
            k-= 1
            if k <= 0: break
        else:
            index-= pivot
    return tuple(result)

def combination_index(combination: tuple, n: int) -> int:
    """Return the index of combination (length == k)

    The combination argument should be a sorted sequence (i | i∈{0…n-1})"""

    k= len(combination)
    index= 0
    item_in_check= 0
    n-= 1 # to simplify subsequent calculations
    for offset, item in enumerate(combination, 1):
        while item_in_check < item:
            index+= fact(n-item_in_check)//fact(k-offset)//fact(n+offset-item_in_check-k)
            item_in_check+= 1
        item_in_check+= 1
    return index

def test():
    for n in range(1, 11):
        for k in range(1, n+1):
            max_index= fact(n)//fact(k)//fact(n-k)
            for i in range(max_index):
                comb= indexed_combination(n, k, i)
                i2= combination_index(comb, n)
                if i2 != i:
                    raise RuntimeError("mismatching n:%d k:%d i:%d≠%d" % (n, k, i, i2))