【问题标题】:Python - Get index of a combinationPython - 获取组合的索引
【发布时间】:2017-07-06 05:43:34
【问题描述】:

例如,我需要生成从 "a" 到 "]]]]]]" 的组合,为此我使用了这个运行良好的 python 脚本。

import itertools

DATA_ALPHA_NUM = 
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&-
()@=+;/!%$\\'\",.<>*^{}#~_[]"
b = 10

for i in range(1,int(b)+1):
   for e in itertools.combinations(DATA_ALPHA_NUM,i): print(''.join(e))

但是现在我需要做相反的事情,例如:如果我给新脚本“1”,它将输出“a”,如果我给 90,它将输出“]”等等。

我写了几个脚本,在少于 737191 个组合的情况下运行良好,但在使用后效果不佳。

编辑:有人写了类似的东西然后删除它几乎是完美的..

alphaNumList = list(DATA_ALPHA_NUM)
alphaNumList.insert(0, "")

result = ["".join(item) for item in 
itertools.islice(itertools.product(alphaNumList, repeat=6), 0,1)]

【问题讨论】:

  • 如果你有内存,你可以按索引将组合存储在字典中。但这会消耗大量内存,是的。
  • @Jean-FrançoisFabre 我知道,但我想在没有循环的情况下做到这一点,我需要非常快速地获取索引..
  • 有人写了类似“result = [”“.join(item) for item in itertools.islice(itertools.product(alphaNumList, repeat=6), 0,1)]”的东西完美但过早停止
  • 是的,但想法就在这里

标签: python loops


【解决方案1】:

你不想要组合。确实,您想要“aa”。但是组合起来,因为你从不会选择两次相同的项目,这不会发生。

所以这里有一个带有“累积产品”的正确版本,事实上,就像 Raymond 对组合所做的那样,我必须计算 (90, 90 + 90 **2, 90 + 90 **2 + 90 **3, .. .) 找出与我正在跟踪的组合相对应的好功率。

请注意,它没有优化,因为我正在对产品进行切片...只是为了检索一个值!

import itertools

alphaNumList = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&-()@=+;/!%$\\'\",.<>*^{}#~_[]")

cumulative = [len(alphaNumList)]
for i in range(1, 10):
    cumulative.append(len(alphaNumList) ** (i+1) + cumulative[i - 1])


def getCombiFromIndex(combiNumber):
    p = 0
    while cumulative[p] < combiNumber:
        p += 1  # WARNING : not robust to combi greater than (10,90) in my case :)
    rest = combiNumber - 1 - (cumulative[p - 1] if p > 0 else 0)
    return "".join([item for item in itertools.islice(itertools.product(alphaNumList, repeat=p + 1), rest, rest + 1)][0])


print(getCombiFromIndex(1))  # "a"
print(getCombiFromIndex(90))  # "]"
print(getCombiFromIndex(91))  # "aa"
print(getCombiFromIndex(800064))  # "ah+1"

更新:我已经添加了在 2 个索引之间检索列表的方法,基于相同的概念,但在这种情况下,最好使用切片 :)

def getCombiListBetween(fromNumber, toNumber):
    combiList = []
    if fromNumber < toNumber:
        pF, pT = 0, 0
        while cumulative[pF] < fromNumber:
            pF += 1
        while cumulative[pT] < toNumber:
            pT += 1
        start = fromNumber - 1 - (cumulative[pF - 1] if pF > 0 else 0)
        end = toNumber - 1
        for p in range(pF, pT + 1):
            combiList += ["".join(item) for item in itertools.islice(itertools.product(alphaNumList, repeat=p + 1), start, min(cumulative[p], end) + 1)]
            start = 0
            end -= cumulative[p]
    else:
        combiList.append(getCombiFromIndex(fromNumber))
    return combiList

print(getCombiListBetween(8189, 8191)) # ['][', ']]', 'aaa']

【讨论】:

    【解决方案2】:

    概述

    这样做的关键是遍历累积组合,直到达到索引。

    解决方案

    from math import factorial
    
    def comb(n, r):
        'Combinations of n things taken r at a time'
        return factorial(n) // (factorial(r) * factorial(n - r))
    
    def nth_combination(population, r, index):
        'Equivalent to list(combinations(population, r))[index]'
        n = len(population)
        c = comb(n, r)
        if index < 0:
            index += c
        if index < 0 or index >= c:
            raise IndexError
        if r < 0 or r > n:
            raise ValueError
        result = []
        while r:
            c, n, r = c*r//n, n-1, r-1
            while index >= c:
                index -= c
                c, n = c*(n-r)//n, n-1
            result.append(population[-1-n])
        return tuple(result)
    

    优化

    如果速度是一个问题,可以构建更快版本的 comb() 函数。

    一种方法是预先计算阶乘,然后在需要时查找它们:

    fact = [1]
    for i in range(1, 1000):
        fact.append(fact[-1] * i)
    
    def comb(n, r):
        return fact[n] // (fact[r] * fact[n - r])
    

    还有另一种方法可以完全避免大阶乘并且不需要辅助存储:

    def comb(n, r):
        c = 1
        r = min(r, n-r)
        for i in range(1, r+1):
            c = c * (n - r + i) // i
        return c
    

    工作原理

    首先将组合分解为其组件组:

    def groups(n, r):
        return [comb(n-i-1, r-1) for i in range(n-r+1)]
    
    >>> comb(8, 3)
    56
    >>> groups(8, 3)
    [21, 15, 10, 6, 3, 1]
    

    这意味着当您一次运行itertools.combinations('ABCDEFGH', 3) 以获取n=8 的字母r=3 时,有56 种组合。前21个以A开头,后15个以B开头,后10个以C开头,后6个以D开头,后3个以E开头,最后1个开头F

    假设您想从 56 个组合中找到第 25 个组合。它属于第二组,所以您的第一个字母是 B

    由于 25 - 21 是 4,因此您需要在 itertools.combinations('CDEFGH', 2) 定义的“B”组的 15 个成员中找到第 4 个组合。只需重复上述过程,直到所有字母都被提取出来。

    测试

    这是一个测试,以确保它给出预期的结果:

    from itertools import combinations
    
    population = 'ABCDEFGH'
    for r in range(len(population) + 1):
        for i, expected in enumerate(combinations(population, r)):
            actual = locate(list(population), r, i)
            assert expected == actual
    

    【讨论】:

    • 哦,好的,谢谢,我之前用阶乘写过一些类似的东西。我明白你的意思我的想法
    • 工作但耗时太长:/
    猜你喜欢
    • 2015-01-24
    • 1970-01-01
    • 2018-06-07
    • 1970-01-01
    • 2023-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    相关资源
    最近更新 更多