Статистика: комбинации в Python - PullRequest
106 голосов
/ 11 июня 2010

Мне нужно вычислить комбинаторные (nCr) в Python, но я не могу найти функцию для этого в библиотеках math, numpy или stat. Что-то вроде функции типа:

comb = calculate_combinations(n, r)

Мне нужно количество возможных комбинаций, а не фактические комбинации, поэтому itertools.combinations меня не интересует.

Наконец, я хочу избежать использования факториалов, поскольку числа, для которых я буду рассчитывать комбинации, могут стать слишком большими, а факториалы будут чудовищными.

Это похоже на ДЕЙСТВИТЕЛЬНО простой ответ на вопрос, однако меня тонут в вопросах о создании всех реальных комбинаций, а это не то, чего я хочу.

Ответы [ 17 ]

106 голосов
/ 12 июня 2010

Почему бы не написать это самостоятельно? Это однострочник или такой:

from operator import mul    # or mul=lambda x,y:x*y
from fractions import Fraction

def nCk(n,k): 
  return int( reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1) )

Тест - печать треугольника Паскаля:

>>> for n in range(17):
...     print ' '.join('%5d'%nCk(n,k) for k in range(n+1)).center(100)
...     
                                                   1                                                
                                                1     1                                             
                                             1     2     1                                          
                                          1     3     3     1                                       
                                       1     4     6     4     1                                    
                                    1     5    10    10     5     1                                 
                                 1     6    15    20    15     6     1                              
                              1     7    21    35    35    21     7     1                           
                           1     8    28    56    70    56    28     8     1                        
                        1     9    36    84   126   126    84    36     9     1                     
                     1    10    45   120   210   252   210   120    45    10     1                  
                  1    11    55   165   330   462   462   330   165    55    11     1               
               1    12    66   220   495   792   924   792   495   220    66    12     1            
            1    13    78   286   715  1287  1716  1716  1287   715   286    78    13     1         
         1    14    91   364  1001  2002  3003  3432  3003  2002  1001   364    91    14     1      
      1    15   105   455  1365  3003  5005  6435  6435  5005  3003  1365   455   105    15     1   
    1    16   120   560  1820  4368  8008 11440 12870 11440  8008  4368  1820   560   120    16     1
>>> 

PS. отредактировано для замены int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) с int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1)), поэтому для больших N / K

оно не будет
105 голосов
/ 11 июня 2010

См. scipy.special.comb (scipy.misc.comb в более старых версиях scipy). Когда exact равно False, он использует функцию gammaln для получения хорошей точности, не занимая много времени В точном случае он возвращает целое число произвольной точности, которое может занять много времени для вычисления.

48 голосов
/ 11 июня 2010

Быстрый поиск по гугл-коду дает (использует формулу из @ Марк Байерса ):

def choose(n, k):
    """
    A fast way to calculate binomial coefficients by Andrew Dalke (contrib).
    """
    if 0 <= k <= n:
        ntok = 1
        ktok = 1
        for t in xrange(1, min(k, n - k) + 1):
            ntok *= n
            ktok *= t
            n -= 1
        return ntok // ktok
    else:
        return 0

choose() в 10 раз быстрее (проверено на всех 0 <=(n, k) <1e3 пар), чем <code>scipy.misc.comb(), если вам нужен точный ответ.

def comb(N,k): # from scipy.comb(), but MODIFIED!
    if (k > N) or (N < 0) or (k < 0):
        return 0L
    N,k = map(long,(N,k))
    top = N
    val = 1L
    while (top > (N-k)):
        val *= top
        top -= 1
    n = 1L
    while (n < k+1L):
        val /= n
        n += 1
    return val
40 голосов
/ 12 июня 2010

Если вам нужны точные результаты и speed, попробуйте gmpy - gmpy.comb должно делать именно то, что вы просите, и это довольно быстро (изКонечно, как автор gmpy, я am предвзятый; -).

27 голосов
/ 23 ноября 2013

Если вы хотите получить точный результат, используйте sympy.binomial. Кажется, это самый быстрый метод, руки вниз.

x = 1000000
y = 234050

%timeit scipy.misc.comb(x, y, exact=True)
1 loops, best of 3: 1min 27s per loop

%timeit gmpy.comb(x, y)
1 loops, best of 3: 1.97 s per loop

%timeit int(sympy.binomial(x, y))
100000 loops, best of 3: 5.06 µs per loop
19 голосов
/ 01 октября 2013

Дословный перевод математического определения во многих случаях вполне адекватен (помня, что Python автоматически использует арифметику больших чисел):

from math import factorial

def calculate_combinations(n, r):
    return factorial(n) // factorial(r) // factorial(n-r)

Для некоторых входных данных, которые я тестировал (например, n = 1000= 500) это было более чем в 10 раз быстрее, чем один вкладыш reduce, предложенный в другом ответе (проголосовавшем в данный момент).С другой стороны, он превосходит фрагмент кода, предоставленный @JF Sebastian.

9 голосов
/ 24 августа 2012

Вот еще одна альтернатива. Первоначально он был написан на C ++, поэтому его можно перенести в C ++ для целого числа с конечной точностью (например, __int64). Преимущество состоит в том, что (1) он включает в себя только целочисленные операции и (2) он избегает раздувания целочисленного значения, выполняя последовательные пары умножения и деления. Я проверил результат с помощью треугольника Паскаля Нас Банова, он получил правильный ответ:

def choose(n,r):
  """Computes n! / (r! (n-r)!) exactly. Returns a python long int."""
  assert n >= 0
  assert 0 <= r <= n

  c = 1L
  denom = 1
  for (num,denom) in zip(xrange(n,n-r,-1), xrange(1,r+1,1)):
    c = (c * num) // denom
  return c

Обоснование: чтобы минимизировать количество умножений и делений, мы переписываем выражение как

    n!      n(n-1)...(n-r+1)
--------- = ----------------
 r!(n-r)!          r!

Чтобы избежать максимально возможного переполнения умножения, мы будем оценивать в следующем порядке СТРОГО, слева направо:

n / 1 * (n-1) / 2 * (n-2) / 3 * ... * (n-r+1) / r

Мы можем показать, что целочисленная арифметическая операция в этом порядке является точной (т.е. нет ошибки округления).

5 голосов
/ 12 сентября 2014

При использовании динамического программирования сложность времени составляет Θ (n * m), а сложность пространства Θ (м):

def binomial(n, k):
""" (int, int) -> int

         | c(n-1, k-1) + c(n-1, k), if 0 < k < n
c(n,k) = | 1                      , if n = k
         | 1                      , if k = 0

Precondition: n > k

>>> binomial(9, 2)
36
"""

c = [0] * (n + 1)
c[0] = 1
for i in range(1, n + 1):
    c[i] = 1
    j = i - 1
    while j > 0:
        c[j] += c[j - 1]
        j -= 1

return c[k]
3 голосов
/ 30 марта 2017

Если ваша программа имеет верхнюю границу n (скажем, n <= N) и нуждается в многократном вычислении nCr (предпочтительно для >> N раз), использование lru_cache может дать вам огромный повышение производительности:

from functools import lru_cache

@lru_cache(maxsize=None)
def nCr(n, r):
    return 1 if r == 0 or r == n else nCr(n - 1, r - 1) + nCr(n - 1, r)

Построение кеша (что делается неявно) занимает до O(N^2) времени. Любые последующие вызовы на nCr вернутся в O(1).

2 голосов
/ 25 октября 2018

Вы можете написать 2 простые функции, которые на самом деле оказываются примерно в 5-8 раз быстрее, чем scipy.special.comb .На самом деле вам не нужно импортировать какие-либо дополнительные пакеты, и функция довольно легко читаема.Хитрость заключается в том, чтобы использовать запоминание для хранения ранее вычисленных значений и использовать определение nCr

# create a memoization dict
memo = {}
def factorial(n):
    """
    Calculate the factorial of an input using memoization
    :param n: int
    :rtype value: int
    """
    if n in [1,0]:
        return 1
    if n in memo:
        return memo[n]
    value = n*fact(n-1)
    memo[n] = value
    return value

def ncr(n, k):
    """
    Choose k elements from a set of n elements - n must be larger than or equal to k
    :param n: int
    :param k: int
    :rtype: int
    """
    return factorial(n)/(factorial(k)*factorial(n-k))

Если мы сравним времена

from scipy.special import comb
%timeit comb(100,48)
>>> 100000 loops, best of 3: 6.78 µs per loop

%timeit ncr(100,48)
>>> 1000000 loops, best of 3: 1.39 µs per loop
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...