查找列表

时间:2018-02-03 22:26:42

标签: python math

我正在尝试解决从Python

列表中获取唯一组合的一般问题

https://www.mathsisfun.com/combinatorics/combinations-permutations-calculator.html数学上我可以看到组合数的公式为n!/r!(n-r)!,其中n是序列的长度,r是要选择的数字。

如以下python所示n为4且r为2:

lst = 'ABCD'
result = list(itertools.combinations(lst, len(lst)/2))
print len(result)
6

以下是帮助函数,以显示我遇到的问题:

def C(lst):
    l = list(itertools.combinations(sorted(lst), len(lst)/2))
    s = set(l)
    print 'actual', len(l), l
    print 'unique', len(s), list(s)

如果我从iPython运行它,我可以这样调用它:

In [41]: C('ABCD')
actual 6 [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
unique 6 [('B', 'C'), ('C', 'D'), ('A', 'D'), ('A', 'B'), ('A', 'C'), ('B', 'D')]

In [42]: C('ABAB')
actual 6 [('A', 'A'), ('A', 'B'), ('A', 'B'), ('A', 'B'), ('A', 'B'), ('B', 'B')]
unique 3 [('A', 'B'), ('A', 'A'), ('B', 'B')]

In [43]: C('ABBB')
actual 6 [('A', 'B'), ('A', 'B'), ('A', 'B'), ('B', 'B'), ('B', 'B'), ('B', 'B')]
unique 2 [('A', 'B'), ('B', 'B')]

In [44]: C('AAAA')
actual 6 [('A', 'A'), ('A', 'A'), ('A', 'A'), ('A', 'A'), ('A', 'A'), ('A', 'A')]
unique 1 [('A', 'A')]

我想得到的是如上所示的唯一计数,但是combinations然后set不会缩放。 当lst n的长度变长时,随着组合变得越来越大,它会变慢。

有没有办法使用数学或Python技巧来解决计算独特组合的问题?

3 个答案:

答案 0 :(得分:4)

这是基于this Math Forum article中概述的生成函数方法的一些Python代码。对于出现在输入中的每个字母,我们创建一个多项式1 + x + x^2 + ... + x^k,其中k是该字母出现的次数。然后我们将这些多项式相乘:得到的多项式的n系数然后告诉你有多少长度为n的组合。

我们将多项式简单地表示为其(整数)系数的列表,第一个系数表示常数项,下一个系数表示x的系数,依此类推。我们需要能够乘以这样的多项式,所以这是一个函数:

def polymul(p, q):
    """
    Multiply two polynomials, represented as lists of coefficients.
    """
    r = [0]*(len(p) + len(q) - 1)
    for i, c in enumerate(p):
        for j, d in enumerate(q):
            r[i+j] += c*d
    return r

有了上述内容,以下函数计算组合数:

from collections import Counter
from functools import reduce

def ncombinations(it, k):
    """
    Number of combinations of length *k* of the elements of *it*.
    """
    counts = Counter(it).values()
    prod = reduce(polymul, [[1]*(count+1) for count in counts], [1])
    return prod[k] if k < len(prod) else 0

在您的示例上对此进行测试:

>>> ncombinations("abcd", 2)
6
>>> ncombinations("abab", 2)
3
>>> ncombinations("abbb", 2)
2
>>> ncombinations("aaaa", 2)
1

在一些较长的例子中,证明这种方法即使对于长期投入也是可行的:

>>> ncombinations("abbccc", 3)  # the math forum example
6
>>> ncombinations("supercalifragilisticexpialidocious", 10)
334640
>>> from itertools import combinations  # double check ...
>>> len(set(combinations(sorted("supercalifragilisticexpialidocious"), 10)))
334640
>>> ncombinations("supercalifragilisticexpialidocious", 20)
1223225
>>> ncombinations("supercalifragilisticexpialidocious", 34)
1
>>> ncombinations("supercalifragilisticexpialidocious", 35)
0
>>> from string import printable
>>> ncombinations(printable, 50)  # len(printable)==100
100891344545564193334812497256
>>> from math import factorial
>>> factorial(100)//factorial(50)**2  # double check the result
100891344545564193334812497256
>>> ncombinations("abc"*100, 100)
5151
>>> factorial(102)//factorial(2)//factorial(100)  # double check (bars and stars)
5151

答案 1 :(得分:3)

组合()的常规递归定义开始,但添加一个测试仅在该级别的前导值未被使用之前递归:

def uniq_comb(pool, r):
    """ Return an iterator over a all distinct r-length
    combinations taken from a pool of values that
    may contain duplicates.

    Unlike itertools.combinations(), element uniqueness
    is determined by value rather than by position.

    """
    if r:
        seen = set()
        for i, item in enumerate(pool):
            if item not in seen:
                seen.add(item)
                for tail in uniq_comb(pool[i+1:], r-1):
                    yield (item,) + tail
    else:
        yield ()

if __name__ == '__main__':
    from itertools import combinations

    pool = 'ABRACADABRA'
    for r in range(len(pool) + 1):
        assert set(uniq_comb(pool, r)) == set(combinations(pool, r))
        assert dict.fromkeys(uniq_comb(pool, r)) == dict.fromkeys(combinations(pool, r))

答案 2 :(得分:0)

这似乎称为多集组合。我也遇到了同样的问题,最后从sympyhere)重写了一个函数。

您将itertools.combinations(p, r)传递给以下函数以直接检索不同的组合,而不是将迭代器传递给类似collections.Counter(p).most_common()的东西。它比过滤所有组合要快得多,而且内存安全!

def counter_combinations(g, n):
    if sum(v for k, v in g) < n or not n:
        yield []
    else:
        for i, (k, v) in enumerate(g):
            if v >= n:
                yield [k]*n
                v = n - 1
            for v in range(min(n, v), 0, -1):
                for j in counter_combinations(g[i + 1:], n - v):
                    rv = [k]*v + j
                    if len(rv) == n:
                        yield rv

这里是一个例子:

from collections import Counter

p = Counter('abracadabra').most_common()
print(p)
c = [_ for _ in counter_combinations(p, 4)]
print(c)
print(len(c))

输出:

[('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]
[['a', 'a', 'a', 'a'], ['a', 'a', 'a', 'b'], ['a', 'a', 'a', 'r'], ['a', 'a', 'a', 'c'], ['a', 'a', 'a', 'd'], ['a', 'a', 'b', 'b'], ['a', 'a', 'b', 'r'], ['a', 'a', 'b', 'c'], ['a', 'a', 'b', 'd'], ['a', 'a', 'r', 'r'], ['a', 'a', 'r', 'c'], ['a', 'a', 'r', 'd'], ['a', 'a', 'c', 'd'], ['a', 'b', 'b', 'r'], ['a', 'b', 'b', 'c'], ['a', 'b', 'b', 'd'], ['a', 'b', 'r', 'r'], ['a', 'b', 'r', 'c'], ['a', 'b', 'r', 'd'], ['a', 'b', 'c', 'd'], ['a', 'r', 'r', 'c'], ['a', 'r', 'r', 'd'], ['a', 'r', 'c', 'd'], ['b', 'b', 'r', 'r'], ['b', 'b', 'r', 'c'], ['b', 'b', 'r', 'd'], ['b', 'b', 'c', 'd'], ['b', 'r', 'r', 'c'], ['b', 'r', 'r', 'd'], ['b', 'r', 'c', 'd'], ['r', 'r', 'c', 'd']]
31