İstatistikler: Python'daki kombinasyonlar


122

Python içinde combinatorials (nCr) hesaplamamız gerekir ama bunu yapmak için işlevi bulamıyorum math, numpyya stat kütüphaneler. Türünün bir işlevi gibi bir şey:

comb = calculate_combinations(n, r)

Olası kombinasyonların sayısına ihtiyacım var, gerçek kombinasyonlara itertools.combinationsdeğil , bu yüzden beni ilgilendirmiyor.

Son olarak, faktöriyelleri kullanmaktan kaçınmak istiyorum, çünkü kombinasyonları hesaplayacağım sayılar çok büyük olabilir ve faktöriyeller korkunç olacaktır.

Bu, cevaplaması GERÇEKTEN kolay bir soru gibi görünüyor, ancak tüm gerçek kombinasyonları oluşturmakla ilgili sorularda boğuluyorum, ki bu istediğim şey değil.

Yanıtlar:


121

Scipy.special.comb'ye bakın (scipy'nin eski sürümlerinde scipy.misc.comb). exactYanlış olduğunda , çok fazla zaman harcamadan iyi bir hassasiyet elde etmek için gammaln işlevini kullanır. Kesin durumda, hesaplanması uzun zaman alabilecek keyfi hassasiyette bir tamsayı döndürür.


5
scipy.misc.combscipy.special.combsürümden beri kullanımdan kaldırıldı 0.10.0.
Dilawar

120

Neden kendin yazmıyorsun? Tek satırlık veya benzeri:

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

Test - Pascal üçgeninin yazdırılması:

>>> 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. ile değiştirilecek int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) şekilde düzenlendi , int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1))böylece büyük N / K için hata olmaz


26
Azaltmak kullanma, yazma şey basit gerektiğini önerdiği için 1 ve pascal üçgeni ile serin demo için
jon_darkstar

6
-1 çünkü bu cevap yanlıştır: factorial (54) / (factorial (54 - 27)) / factorial (27) == nCk (54, 27), False verir.
robert king

3
@robertking - Tamam, ikiniz de önemsiz ve teknik olarak haklıydınız. Yaptığım şey, kişinin kendi işlevini nasıl yazacağının bir örneğiydi; Kayan nokta hassasiyeti nedeniyle yeterince büyük N ve K için doğru olmadığını biliyordum. Ama bunu düzeltebiliriz - yukarıya bakın, şimdi büyük sayılarda hata olmamalı
Nas Banov

9
Bu muhtemelen Haskell'de hızlı olacaktır, ancak ne yazık ki Python değil. Aslında diğer cevapların çoğuna kıyasla oldukça yavaş, örneğin @Alex Martelli, JF Sebastian ve benimki.
Todd Owen

9
Python 3 için de mecbur kaldım from functools import reduce.
Velizar Hristov

52

Google kodunda hızlı bir arama ( @Mark Byers'ın cevabındaki formülü kullanır ):

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()tam bir yanıta ihtiyacınız olduğundan 10 kat daha hızlıdır (tüm 0 <= (n, k) <1e3 çiftlerinde test edilmiştir) .

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

Paket gerektirmeyen güzel bir çözüm
Edward Newell

2
Bilginize: Bahsedilen formül burada: en.wikipedia.org/wiki/…
jmiserez

Bu chooseişlevin çok daha fazla oy alması gerekir! Python 3.8'de math.comb var, ancak bir meydan okuma için Python 3.6 kullanmak zorunda kaldım ve hiçbir uygulama çok büyük tam sayılar için kesin sonuçlar vermedi. Bu hızlı yapıyor ve yapıyor!
bağlan

42

Kesin sonuçlar ve hız istiyorsanız , gmpy'yi deneyin - gmpy.combtam olarak ne istediğinizi yapmalısınız ve o kadar tabii (oldukça hızlı gmpy, ben 'nin orijinal yazarın am ;-) önyargılı.


6
Doğrusu, kod için gmpy2.comb()choose()for k, n in itertools.combinations(range(1000), 2): f(n,k)f()gmpy2.comb()choose()
cevabımdan

Paketin yazarı olduğunuz için, bozuk bağlantıyı düzeltmenize izin vereceğim , böylece doğru yeri
gösterecektir

@SeldomNeedy, code.google.com link tek (site artık arşiv modunda olduğunda bile) doğru yer. Elbette oradan github konumunu, github.com/aleaxit/gmpy ve PyPI birini, pypi.python.org/pypi/gmpy2'yi bulmak kolaydır , çünkü ikisine de bağlıdır! -)
Alex Martelli

@AlexMartelli Karışıklık için özür dilerim. Javascript (seçici olarak) devre dışı bırakıldıysa, sayfa bir 404 görüntüler. Sanırım bu, sahte AI'ların arşivlenmiş Google Code Project kaynaklarını bu kadar kolay bir şekilde dahil etmelerini engellemek için?
SeldomNeedy

28

Kesin bir sonuç istiyorsanız, kullanın sympy.binomial. En hızlı yöntem gibi gözüküyor, eller aşağı.

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

Pek çok durumda matematiksel tanımın birebir çevirisi oldukça yeterlidir (Python'un otomatik olarak büyük sayı aritmetiğini kullanacağını unutmayın):

from math import factorial

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

Test ettiğim bazı girdiler için (örneğin n = 1000 r = 500) bu, reducebaşka bir cevapta (şu anda en yüksek oyu alan) önerilen bir satırdan 10 kat daha hızlıydı . Öte yandan, @JF Sebastian tarafından sağlanan Snippit tarafından sahnelendi.


11

Başlangıç ​​olarak Python 3.8, standart kitaplık artık math.combiki terimli katsayıyı hesaplama işlevini içermektedir :

math.comb (n, k)

n öğeden k öğeyi tekrar etmeden seçmenin yollarının sayısı
n! / (k! (n - k)!):

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

10

İşte başka bir alternatif. Bu başlangıçta C ++ ile yazılmıştır, bu nedenle sonlu kesinlikli bir tamsayı için C ++ 'ya geri aktarılabilir (örneğin __int64). Avantajı, (1) yalnızca tamsayı işlemlerini içerir ve (2) ardışık çarpma ve bölme çiftleri yaparak tamsayı değerini şişirmekten kaçınır. Sonucu Nas Banov'un Pascal üçgeni ile test ettim, doğru cevabı alıyor:

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

Gerekçe: Çarpma ve bölme sayısını en aza indirmek için ifadeyi şu şekilde yeniden yazıyoruz:

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

Çarpma taşmasını olabildiğince önlemek için, soldan sağa aşağıdaki STRICT sırasına göre değerlendireceğiz:

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

Bu sırada çalıştırılan tamsayı aritmatikin kesin olduğunu gösterebiliriz (yani yuvarlama hatası yok).


5

Dinamik programlama kullanıldığında, zaman karmaşıklığı Θ (n * m) ve uzay karmaşıklığı Θ (m) 'dir:

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

Programınız bir üst bağlı ise n(söz hakkından n <= N) ve ihtiyaçları sürekli (tercihen için >> nCr hesaplamak için Nzaman) kullanarak lru_cache size büyük bir performans artışı sağlayacaktır:

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)

Önbelleğin oluşturulması (örtük olarak yapılır) O(N^2)zaman alır . Sonraki tüm çağrılar nCrgeri dönecektir O(1).


4

Aslında scipy.special.comb'yi kullanmaktan 5-8 kat daha hızlı çıkan 2 basit işlev yazabilirsiniz . Aslında, herhangi bir ekstra paketi içe aktarmanıza gerek yoktur ve işlev oldukça kolay okunabilir. İşin püf noktası, önceden hesaplanmış değerleri saklamak için hafızayı kullanmak ve nCr tanımını kullanmaktır.

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

Zamanları karşılaştırırsak

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

Bu günlerde lru_cache adında kodunuzu basitleştirebilecek bir memoize dekoratörü var mı?
demented hedgehog

2

Sympy ile oldukça kolaydır.

import sympy

comb = sympy.binomial(n, r)

2

Yalnızca Python ile dağıtılan standart kitaplığı kullanarak :

import itertools

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

3
Zaman karmaşıklığının (ve bellek kullanımının) kabul edilebilir olduğunu düşünmüyorum.
xmcp

2

Doğrudan formül, n 20'den büyük olduğunda büyük tamsayılar üretir.

Öyleyse, bir başka yanıt daha:

from math import factorial

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

kısa, doğru ve verimli çünkü bu, uzunlara bağlı kalarak python büyük tam sayıları önler.

Scipy.special.comb ile karşılaştırıldığında daha doğru ve daha hızlıdır:

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

Bu yanlış! Eğer n == r ise sonuç 1 olmalıdır. Bu kod 0
değerini

Daha doğrusu, bunun range(n-r+1, n+1)yerine olmalıdır range(n-r,n+1).
reyammer

1

Bu, yerleşik hatırlatma dekoratörünü kullanan @ killerT2333 kodudur.

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

İşte sizin için verimli bir algoritma

for i = 1.....r

   p = p * ( n - i ) / i

print(p)

Örneğin nCr (30,7) = fact (30) / (fact (7) * fact (23)) = (30 * 29 * 28 * 27 * 26 * 25 * 24) / (1 * 2 * 3 * 4 * 5 * 6 * 7)

Yani 1'den r'ye kadar olan döngüyü çalıştırmanız sonucu elde edebilirsiniz.


0

Bu muhtemelen oldukça büyük girdiler için saf python'da yapabileceğiniz kadar hızlıdır:

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

Bu işlev çok iyimser.

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.