Thống kê: các kết hợp trong Python


122

Tôi cần phải tính toán combinatorials (nCr) bằng Python nhưng không thể tìm thấy chức năng để làm điều đó trong math, numpyhoặc stat thư viện. Một cái gì đó giống như một chức năng của loại:

comb = calculate_combinations(n, r)

Tôi cần số lượng các kết hợp có thể có, không phải các kết hợp thực tế, vì vậy itertools.combinationstôi không quan tâm.

Cuối cùng, tôi muốn tránh sử dụng các giai thừa, vì những con số tôi sẽ tính toán các kết hợp có thể quá lớn và các giai thừa sẽ rất quái dị.

Đây có vẻ như là một câu hỏi THỰC SỰ dễ trả lời, tuy nhiên tôi đang bị chìm trong các câu hỏi về việc tạo ra tất cả các kết hợp thực tế, đó không phải là điều tôi muốn.

Câu trả lời:


121

Xem scipy.special.comb (scipy.misc.comb trong các phiên bản cũ của scipy). Khi nào exactlà Sai, nó sử dụng hàm gammaln để có được độ chính xác tốt mà không mất nhiều thời gian. Trong trường hợp chính xác, nó trả về một số nguyên có độ chính xác tùy ý, có thể mất nhiều thời gian để tính toán.


5
scipy.misc.combkhông được chấp nhận thay scipy.special.combvì phiên bản kể từ 0.10.0.
Dilawar

120

Tại sao không viết nó cho mình? Đó là một lớp lót hoặc tương tự:

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

Kiểm tra - in tam giác 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
>>> 

Tái bút. đã chỉnh sửa để thay thế int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) bằng int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1))để nó không bị lỗi cho N / K lớn


26
1 cho thấy để ghi một cái gì đó đơn giản, sử dụng giảm, và cho bản demo mát mẻ với tam giác pascal
jon_darkstar

6
-1 vì câu trả lời này sai: print Giai thừa (54) / (Giai thừa (54 - 27)) / Giai thừa (27) == nCk (54, 27) cho kết quả Sai.
robert king

3
@robertking - Ok, bạn vừa nhỏ vừa đúng kỹ thuật. Những gì tôi đã làm chỉ là minh họa cho cách viết hàm của chính mình; tôi biết rằng nó không chính xác cho N và K đủ lớn do độ chính xác dấu phẩy động. Nhưng chúng ta có thể khắc phục điều đó - xem ở trên, bây giờ nó không nên err cho số lớn
Nas Banov

9
Điều này có thể sẽ nhanh trong Haskell, nhưng không may là Python. Nó thực sự khá chậm so với nhiều câu trả lời khác, ví dụ như @Alex Martelli, JF Sebastian và của riêng tôi.
Todd Owen

9
Đối với Python 3, tôi cũng phải làm như vậy from functools import reduce.
Velizar Hristov

52

Một tìm kiếm nhanh trên mã google cho kết quả (nó sử dụng công thức từ câu trả lời của @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()nhanh hơn 10 lần (được thử nghiệm trên tất cả các cặp 0 <= (n, k) <1e3) so với scipy.misc.comb()khi bạn cần câu trả lời chính xác.

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

Một giải pháp tốt đẹp mà không đòi hỏi bất kỳ pkg
Edward Newell

2
FYI: Công thức được đề cập ở đây: en.wikipedia.org/wiki/…
jmiserez

chooseChức năng này sẽ có nhiều phiếu bầu hơn! Python 3.8 có math.comb, nhưng tôi đã phải sử dụng Python 3.6 cho một thử thách và không có triển khai nào cho kết quả chính xác cho các số nguyên rất lớn. Điều này làm và làm nó nhanh chóng!
gọi lại

42

Nếu bạn muốn kết quả chính xác tốc độ, hãy thử gmpy - gmpy.combnên làm chính xác những gì bạn yêu cầu, nó khá nhanh (tất nhiên, như gmpycủa tác giả ban đầu, tôi đang thiên vị ;-).


6
Thật vậy, gmpy2.comb()nhanh hơn 10 lần choose()từ câu trả lời của tôi cho các mã: for k, n in itertools.combinations(range(1000), 2): f(n,k)nơi f()là một trong hai gmpy2.comb()hoặc choose()trên Python 3.
JFS

Vì bạn là tác giả của gói, nên tôi sẽ cho bạn sửa liên kết bị hỏng để nó trỏ đến đúng nơi ....
SeldomNeedy

@SeldomNeedy, liên kết đến code.google.com là một nơi phù hợp (mặc dù trang web hiện đang ở chế độ lưu trữ). Tất nhiên từ đó, thật dễ dàng tìm thấy vị trí github, github.com/aleaxit/gmpy và PyPI một, pypi.python.org/pypi/gmpy2 , vì nó liên kết với cả hai! -)
Alex Martelli

@AlexMartelli Xin lỗi vì sự nhầm lẫn. Trang này hiển thị 404 nếu javascript đã bị vô hiệu hóa (có chọn lọc). Tôi đoán điều đó để ngăn cản những AI giả mạo kết hợp các nguồn Dự án mã Google đã lưu trữ một cách khá dễ dàng?
Ít khi cần thiết vào

28

Nếu bạn muốn một kết quả chính xác, hãy sử dụng sympy.binomial. Nó có vẻ là phương pháp nhanh nhất, xuống tay.

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

Bản dịch theo nghĩa đen của định nghĩa toán học là khá đầy đủ trong nhiều trường hợp (hãy nhớ rằng Python sẽ tự động sử dụng số học lớn):

from math import factorial

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

Đối với một số đầu vào tôi đã kiểm tra (ví dụ: n = 1000 r = 500), điều này nhanh hơn 10 lần so với một lớp lót được reduceđề xuất trong một câu trả lời khác (hiện được bình chọn cao nhất). Mặt khác, nó được thực hiện bởi đoạn trích được cung cấp bởi @JF Sebastian.


11

Bắt đầu Python 3.8, thư viện chuẩn bây giờ bao gồm math.combchức năng tính toán hệ số nhị thức:

math.comb (n, k)

là số cách chọn k mục từ n mục mà không lặp lại
n! / (k! (n - k)!):

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

10

Đây là một giải pháp thay thế khác. Cái này ban đầu được viết bằng C ++, vì vậy nó có thể được báo cáo lại cho C ++ để lấy một số nguyên có độ chính xác hữu hạn (ví dụ: __int64). Ưu điểm là (1) nó chỉ liên quan đến các phép toán số nguyên và (2) nó tránh làm phình giá trị số nguyên bằng cách thực hiện các cặp phép nhân và phép chia liên tiếp. Tôi đã kiểm tra kết quả với tam giác Pascal của Nas Banov, nó nhận được câu trả lời chính xác:

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

Cơ sở lý luận: Để giảm thiểu số phép nhân và phép chia, chúng tôi viết lại biểu thức dưới dạng

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

Để tránh bội số nhân nhiều nhất có thể, chúng tôi sẽ đánh giá theo thứ tự NGHIÊM TÚC sau, từ trái sang phải:

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

Chúng ta có thể chỉ ra rằng số nguyên được vận hành theo thứ tự này là chính xác (tức là không có lỗi làm tròn).


5

Sử dụng lập trình động, độ phức tạp thời gian là Θ (n * m) và độ phức tạp không gian Θ (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ếu chương trình của bạn có giới hạn trên là n(giả sử n <= N) và cần phải tính toán nhiều lần nCr (tốt nhất là >> Nlần), việc sử dụng lru_cache có thể giúp bạn tăng hiệu suất rất lớn:

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)

Việc xây dựng bộ nhớ cache (được thực hiện ngầm) mất nhiều O(N^2)thời gian. Mọi cuộc gọi tiếp theo nCrsẽ quay trở lại O(1).


4

Bạn có thể viết 2 hàm đơn giản thực sự nhanh hơn khoảng 5-8 lần so với việc sử dụng scipy.special.comb . Trên thực tế, bạn không cần phải nhập bất kỳ gói bổ sung nào và chức năng này khá dễ đọc. Mẹo là sử dụng ghi nhớ để lưu trữ các giá trị đã tính toán trước đó và sử dụng định nghĩa của 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))

Nếu chúng ta so sánh thời gian

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

Ngày nay, có một trình trang trí ghi nhớ trong functools được gọi là lru_cache có thể đơn giản hóa mã của bạn?
hedgehog mất

2

Nó khá dễ dàng với giao hưởng.

import sympy

comb = sympy.binomial(n, r)

2

Chỉ sử dụng thư viện chuẩn được phân phối với Python :

import itertools

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

3
tôi không nghĩ rằng độ phức tạp về thời gian của nó (và việc sử dụng bộ nhớ) có thể chấp nhận được.
xmcp

2

Công thức trực tiếp tạo ra số nguyên lớn khi n lớn hơn 20.

Vì vậy, một phản hồi khác:

from math import factorial

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

ngắn gọn, chính xác và hiệu quả vì điều này tránh các số nguyên lớn của python bằng cách gắn bó với các giá trị dài.

Nó chính xác hơn và nhanh hơn khi so sánh với 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

Cái này sai! Nếu n == r, kết quả nên 1. Mã này trở về 0.
reyammer

Chính xác hơn, nó nên được range(n-r+1, n+1)thay vì range(n-r,n+1).
reyammer

1

Đây là mã @ killerT2333 sử dụng trình trang trí ghi nhớ tích hợp.

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

Đây là một thuật toán hiệu quả dành cho bạn

for i = 1.....r

   p = p * ( n - i ) / i

print(p)

Ví dụ: nCr (30,7) = fact (30) / (fact (7) * fact (23)) = (30 * 29 * 28 * 27 * 26 * 25 * 24) / (1 * 2 * 3 * 4 * 5 * 6 * 7)

Vì vậy chỉ cần chạy vòng lặp từ 1 đến r là có thể nhận được kết quả.


0

Điều đó có thể nhanh như bạn có thể làm trong python thuần túy để có đầu vào lớn hợp lý:

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

Chức năng này được tối ưu hóa rất tốt.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.