통계 : Python의 조합


122

Python에서 조합 (nCr)을 계산해야하지만 math, numpy또는 stat 라이브러리 에서이를 수행 할 함수를 찾을 수 없습니다 . 유형의 함수와 같은 것 :

comb = calculate_combinations(n, r)

실제 조합이 아닌 가능한 조합의 수가 필요하므로 itertools.combinations관심이 없습니다.

마지막으로, 조합을 계산할 숫자가 너무 커지고 팩토리얼이 엄청나게 클 수 있으므로 팩토리얼 사용을 피하고 싶습니다.

이것은 정말 대답하기 쉬운 질문처럼 보이지만 내가 원하는 것이 아닌 모든 실제 조합을 생성하는 것에 대한 질문에 빠져 있습니다.

답변:


121

scipy.special.comb (이전 버전의 scipy에서는 scipy.misc.comb)를 참조하십시오 . 때 exact거짓, 그것은 많은 시간을 복용하지 않고 좋은 정밀도를 얻기 위해 GAMMALN 함수를 사용합니다. 정확한 경우에는 계산하는 데 시간이 오래 걸릴 수있는 임의 정밀도 정수를 반환합니다.


5
scipy.misc.combscipy.special.comb이후 버전 대신 사용되지 않습니다 0.10.0.
Dilawar

120

직접 작성하지 않으시겠습니까? 한 줄짜리입니다.

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) )

테스트-Pascal의 삼각형 인쇄 :

>>> 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
>>> 

추신. 대체 편집 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))그렇게하지 ERR 큰 N / K에 대한 것입니다


26
+1 작성 제안, reduce 사용, 파스칼 삼각형의 멋진 데모
jon_darkstar

6
-1이 대답이 틀 렸기 때문에 : print factorial (54) / (factorial (54-27)) / factorial (27) == nCk (54, 27)은 False를 제공합니다.
로버트 킹

3
@robertking-좋아, 당신은 사소하고 기술적으로 정확했습니다. 내가 한 것은 자신의 함수를 작성하는 방법을 설명하는 것입니다. 나는 그것이 부동 소수점 정밀도로 인해 충분히 큰 N과 K에 대해 정확하지 않다는 것을 알았습니다. 그러나 우리는 그 문제를 해결할 수 - 위 참조, 지금하지 ERR 큰 숫자해야
나스 Banov

9
이것은 아마도 Haskell에서 빠르지 만 불행히도 Python은 아닙니다. @Alex Martelli, JF Sebastian 및 내 답변과 같은 다른 많은 답변에 비해 실제로는 상당히 느립니다.
Todd Owen

9
Python 3의 경우 from functools import reduce.
Velizar Hristov

52

Google 코드에 대한 빠른 검색은 다음과 같습니다 ( @Mark Byers의 답변의 공식을 사용합니다 ).

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()scipy.misc.comb()정확한 답이 필요한 경우 보다 10 배 더 빠릅니다 (모든 0 <= (n, k) <1e3 쌍에서 테스트 됨) .

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

패키지가 필요없는 멋진 솔루션
Edward Newell

2
참고 : 언급 된 공식은 여기에 있습니다 : en.wikipedia.org/wiki/...
jmiserez은

choose함수는 더 많은 찬성표를 가져야합니다! Python 3.8에는 math.comb가 있지만 문제를 해결하기 위해 Python 3.6을 사용해야했으며 매우 큰 정수에 대해 정확한 결과를 제공하는 구현은 없습니다. 이것은 빠르게 수행합니다!
정찰

42

당신이 정확한 결과를 원하는 경우 속도를 시도 gmpy - gmpy.comb당신이 무엇을 물어 정확히 어떻게해야 하고 그것으로, 물론 (꽤 빨리 gmpy, 나는 '의 원래 저자 입니다 ;-) 바이어스.


6
사실, 코드에 대한 내 대답 gmpy2.comb()보다 10 배 더 빠릅니다 choose(): for k, n in itertools.combinations(range(1000), 2): f(n,k)where f()is either gmpy2.comb()or choose()on Python 3
jfs

패키지의 저자 것 때문에, 나는 드리겠습니다 당신이 그것을 .... 올바른 위치를 가리 키도록 깨진 링크를 수정
SeldomNeedy

@SeldomNeedy, code.google.com에 대한 링크는 하나 개 (사이트가 현재 보관 모드에 불구하고) 바로 장소. 거기에서 물론 그것은 github에 위치, 쉽게 찾을 수 github.com/aleaxit/gmpy 하고 PyPI 하나, pypi.python.org/pypi/gmpy2을 가 모두 링크로, -!)
알렉스 마르 텔리

@AlexMartelli 혼란을 드려 죄송합니다. 자바 스크립트가 (선택적으로) 비활성화 된 경우 페이지에 404가 표시됩니다. 이것이 악의적 인 AI가 보관 된 Google 코드 프로젝트 소스를 아주 쉽게 통합하는 것을 막기위한 것 같습니다.
SeldomNeedy

28

정확한 결과를 원하면 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

22

수학적 정의를 문자 그대로 번역하면 많은 경우에 매우 적합합니다 (파이썬이 자동으로 큰 숫자 산술을 사용한다는 점을 기억하십시오).

from math import factorial

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

내가 테스트 한 일부 입력 (예 : n = 1000 r = 500)의 경우 이것은 reduce다른 (현재 가장 많이 득표 한) 답변에서 제안 된 한 라이너보다 10 배 이상 빠릅니다 . 반면에 @JF Sebastian이 제공하는 스 니핏보다 성능이 뛰어납니다.


11

시작 Python 3.8하면 표준 라이브러리 math.comb에 이항 계수를 계산하는 함수가 포함됩니다 .

math.comb (n, k)

반복하지 않고 n 항목에서 k 항목을 선택하는 방법의 수입니다
n! / (k! (n - k)!).

import math
math.comb(10, 5) # 252

10

다른 대안이 있습니다. 이것은 원래 C ++로 작성되었으므로 유한 정밀도 정수 (예 : __int64)를 위해 C ++로 백 포트 할 수 있습니다. 장점은 (1) 정수 연산 만 포함하고 (2) 연속적인 곱셈과 나눗셈 쌍을 수행하여 정수 값의 팽창을 방지한다는 것입니다. Nas Banov의 Pascal 삼각형으로 결과를 테스트했는데 정답을 얻었습니다.

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!

곱셈 오버플로를 최대한 피하기 위해 왼쪽에서 오른쪽으로 다음 STRICT 순서로 평가합니다.

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

이 순서로 연산 된 정수 산술이 정확하다는 것을 보여줄 수 있습니다 (즉, 반올림 오류가 없음).


5

동적 프로그래밍을 사용하면 시간 복잡도는 Θ (n * m)이고 공간 복잡도는 Θ (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]

4

프로그램의 상한이 n(예 :)이고 nCrn <= N 을 반복적으로 계산해야하는 경우 (가급적 >> 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)시간 이 많이 걸립니다 . 에 대한 후속 호출 nCrO(1).


4

scipy.special.comb를 사용 하는 것보다 실제로 약 5-8 배 빠른 것으로 밝혀진 2 개의 간단한 함수를 작성할 수 있습니다 . 사실, 추가 패키지를 가져올 필요가 없으며 함수를 매우 쉽게 읽을 수 있습니다. 비결은 메모 화를 사용하여 이전에 계산 된 값을 저장하고 nCr 의 정의를 사용하는 것입니다.

# create a memoization dictionary
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*factorial(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

요즘에는 코드를 단순화 할 수있는 lru_cache라는 functools에 memoize 데코레이터가 있습니다.
고슴도치 미친

2

sympy를 사용하면 매우 쉽습니다.

import sympy

comb = sympy.binomial(n, r)

2

Python으로 배포 된 표준 라이브러리 만 사용 :

import itertools

def nCk(n, k):
    return len(list(itertools.combinations(range(n), k)))

3
나는 그것의 시간 복잡성 (및 메모리 사용량)이 수용 가능하다고 생각하지 않는다.
xmcp 2017-04-30

2

직접 공식은 n이 20보다 클 때 큰 정수를 생성합니다.

그래서 또 다른 응답 :

from math import factorial

reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)

이것은 long을 고수함으로써 파이썬 큰 정수를 피하기 때문에 짧고 정확하고 효율적입니다.

scipy.special.comb와 비교할 때 더 정확하고 빠릅니다.

 >>> from scipy.special import comb
 >>> nCr = lambda n,r: reduce(long.__mul__, range(n-r+1, n+1), 1L) // factorial(r)
 >>> comb(128,20)
 1.1965669823265365e+23
 >>> nCr(128,20)
 119656698232656998274400L  # accurate, no loss
 >>> from timeit import timeit
 >>> timeit(lambda: comb(n,r))
 8.231969118118286
 >>> timeit(lambda: nCr(128, 20))
 3.885951042175293

이것은 잘못되었습니다! N == r은, 결과는이 코드를 반환 0 1. 할 필요가있는 경우
reyammer

보다 정확하게 range(n-r+1, n+1)range(n-r,n+1).
reyammer

1

내장 된 메모 데코레이터를 사용하는 @ killerT2333 코드입니다.

from functools import lru_cache

@lru_cache()
def factorial(n):
    """
    Calculate the factorial of an input using memoization
    :param n: int
    :rtype value: int
    """
    return 1 if n in (1, 0) else n * factorial(n-1)

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

print(ncr(6, 3))

1

다음은 효율적인 알고리즘입니다.

for i = 1.....r

   p = p * ( n - i ) / i

print(p)

예를 들어 nCr (30,7) = fact (30) / (fact (7) * fact (23)) = (30 * 29 * 28 * 27 * 26 * 25 * 24) / (1 * 2 * 3 * 4 * 5 * 6 * 7)

따라서 1에서 r까지 루프를 실행하면 결과를 얻을 수 있습니다.


0

상당히 큰 입력을 위해 순수 파이썬에서 할 수있는 것만 큼 빠를 것입니다.

def choose(n, k):
    if k == n: return 1
    if k > n: return 0
    d, q = max(k, n-k), min(k, n-k)
    num =  1
    for n in xrange(d+1, n+1): num *= n
    denom = 1
    for d in xrange(1, q+1): denom *= d
    return num / denom

0

이 기능은 매우 최적화되어 있습니다.

def nCk(n,k):
    m=0
    if k==0:
        m=1
    if k==1:
        m=n
    if k>=2:
        num,dem,op1,op2=1,1,k,n
        while(op1>=1):
            num*=op2
            dem*=op1
            op1-=1
            op2-=1
        m=num//dem
    return m
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.