Python - 获取组合的索引

时间:2017-07-02 16:11:05

标签: python loops

我需要从“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)]

2 个答案:

答案 0 :(得分:1)

概述

关键是要遍历累积组合,直到达到指数。

解决方案

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,下一个开始是B,下一个开始是C,下一个开始是D,下一个开始是E {1}},最后一个以F开头。

假设你想找到56中的第25个组合。那就是第二组,所以你的第一个字母是B

由于25-21岁是4岁,你需要在&#34; B&#34;的15个成员中找到第4个组合。由itertools.combinations('CDEFGH', 2)定义的组。只需重复上述过程,直到提取完所有字母。

测试

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

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

答案 1 :(得分:1)

你不想要组合。的确,你想要“aa”。但是对于组合,因为你永远不会选择TWICE相同的项目,所以不会发生这种情况。

所以这里有一个带有“累积产品”的正确版本,事实上,正如雷蒙德的组合,我必须计算(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']