सांख्यिकी: पायथन में संयोजन


122

मैं अजगर में combinatorials (एनसीआर) की गणना करने की जरूरत है, लेकिन ऐसा करने के लिए कि में समारोह नहीं मिल सकता है math, numpyया stat पुस्तकालयों। प्रकार के एक समारोह की तरह कुछ:

comb = calculate_combinations(n, r)

मुझे संभावित संयोजनों की संख्या की आवश्यकता है, न कि वास्तविक संयोजनों की, इसलिए itertools.combinationsमेरी रुचि नहीं है।

अंत में, मैं factorials का उपयोग करने से बचना चाहता हूं, क्योंकि मैं जिन संयोजनों की गणना कर रहा हूं, वे बहुत बड़े हो सकते हैं और factorial राक्षसी होने जा रहे हैं।

यह सवाल का जवाब देने के लिए वास्तव में आसान लगता है, हालांकि मैं सभी वास्तविक संयोजनों को बनाने के बारे में सवालों में डूब रहा हूं, जो कि मैं नहीं चाहता।

जवाबों:


121

Scipy.special.comb (scipy.misc.comb को scipy के पुराने संस्करणों में देखें )। जब exactगलत है, तो यह बहुत समय लेने के बिना अच्छी सटीकता प्राप्त करने के लिए gammaln फ़ंक्शन का उपयोग करता है। सटीक स्थिति में यह एक मनमाना-सटीक पूर्णांक देता है, जिसे गणना करने में लंबा समय लग सकता है।


5
scipy.misc.combscipy.special.combसंस्करण के पक्ष में पदावनत किया जाता है 0.10.0
दिलावर

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

परीक्षण - मुद्रण पास्कल त्रिकोण:

>>> 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))तो यह बड़ा N / कश्मीर के लिए गलती नहीं करेंगे


26
+1 कम करने के लिए, और पास्कल त्रिकोण के साथ शांत डेमो के लिए कुछ सरल लिखने के सुझाव के लिए
jon_darkstar

6
-1 क्योंकि यह उत्तर गलत है: प्रिंट फैक्टोरियल (54) / (factorial (54 - 27)) / factorial (27) == nCk (54, 27) फाल्स देता है।
रोबर्ट राजा

3
@robertking - ठीक है, आप दोनों क्षुद्र और तकनीकी रूप से सही थे। मैंने जो किया उसका मतलब यह था कि किसी के स्वयं के कार्य को कैसे लिखा जाए; मुझे पता था कि फ्लोटिंग पॉइंट सटीक होने के कारण यह बड़े N और K के लिए सटीक नहीं है। लेकिन हम इसे ठीक कर सकते हैं - ऊपर देखें, अब इसे बड़ी संख्या के लिए
गलत

9
यह संभवतः हास्केल में तेज़ होगा, लेकिन दुर्भाग्य से पायथन नहीं। यह वास्तव में अन्य उत्तरों के कई उदाहरणों की तुलना में काफी धीमा है, जैसे @Alex Martelli, JF सेबेस्टियन, और मेरे अपने।
टॉड ओवेन

9
पायथन 3 के लिए, मुझे भी करना था from functools import reduce
वेलिज़र हिस्त्रोव

52

Google कोड पर एक त्वरित खोज (यह @Mark बायर्स के उत्तर से सूत्र का उपयोग करती है ):

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

एक अच्छा समाधान जिसे किसी भी pkg की आवश्यकता नहीं है
एडवर्ड नेवेल

2
FYI करें: उल्लिखित सूत्र यहाँ है: en.wikipedia.org/wiki/…
jmiserez

इस chooseफ़ंक्शन में अधिक अप-वोट होने चाहिए! Python 3.8 में math.comb है, लेकिन मुझे एक चुनौती के लिए Python 3.6 का उपयोग करना पड़ा और किसी भी कार्यान्वयन ने बहुत पूर्ण पूर्णांक के लिए सटीक परिणाम नहीं दिए। यह एक और तेजी से करता है!
reconn

42

आप सटीक परिणाम चाहते हैं और गति, कोशिश gmpy - gmpy.combवास्तव में क्या करना चाहिए कि आप क्या पूछते हैं, और यह बहुत तेजी से (बेशक है, के रूप में gmpy, मैं के मूल लेखक हूँ पक्षपाती ;-)।


6
वास्तव में, कोड के लिए मेरे जवाब से gmpy2.comb()10 गुना तेज है choose(): for k, n in itertools.combinations(range(1000), 2): f(n,k)जहां या f()तो पायथन 3 पर हैgmpy2.comb()choose()
jfs

जब से तुम पैकेज के लेखक हैं, मैं दूँगा तुम्हें तो यह सही जगह के लिए अंक .... टूट लिंक को ठीक
SeldomNeedy

@SeldomNeedy, code.google.com का लिंक एक सही जगह है (हालाँकि साइट अब अभिलेखीय मोड में है)। बेशक वहाँ से यह आसानी से github स्थान, github.com/aleaxit/gmpy , और PyPI एक, pypi.python.org/pypi/gmpy2 को ढूंढना आसान है , क्योंकि यह दोनों को जोड़ता है!)
एलेक्स

@AlexMartelli भ्रम के लिए क्षमा करें। यदि जावास्क्रिप्ट (चुनिंदा) अक्षम किया गया है तो पृष्ठ 404 प्रदर्शित करता है। मुझे लगता है कि दुष्ट AIs को संग्रहीत Google कोड प्रोजेक्ट स्रोतों को शामिल करने से इतनी आसानी से हतोत्साहित करना है?
सेलेडमनीडे

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) यह एक लाइनर की तुलना में 10 गुना अधिक तेज था, जो reduceदूसरे (वर्तमान में सर्वाधिक मतदान) उत्तर में सुझाया गया था। दूसरी ओर, यह @JF सेबेस्टियन द्वारा प्रदान किए गए स्निपिट द्वारा किया जाता है।


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 के पास्कल त्रिकोण के साथ परिणाम का परीक्षण किया है, इसे सही उत्तर मिलता है:

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 (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(कहना n <= N) है और आपको बार- Nबार nCr (अधिमानतः >> बार) की गणना करने की आवश्यकता है , तो 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)


4

आप 2 सरल कार्य लिख सकते हैं जो वास्तव में scipy.special.comb का उपयोग करने की तुलना में लगभग 5-8 गुना तेजी से निकलता है । वास्तव में, आपको किसी भी अतिरिक्त पैकेज को आयात करने की आवश्यकता नहीं है, और फ़ंक्शन काफी आसानी से पठनीय है। चाल पहले गणना मूल्यों को संग्रहीत करने के लिए संस्मरण का उपयोग करना है, और 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 नामक फंक्शंस में एक ज्ञापन सज्जाकार है जो आपके कोड को सरल बना सकता है?
हेज


2

पायथन के साथ वितरित केवल मानक पुस्तकालय का उपयोग करना :

import itertools

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

3
मुझे नहीं लगता कि इसकी समय जटिलता (और स्मृति उपयोग) स्वीकार्य है।
xmcp

2

प्रत्यक्ष सूत्र 20 से बड़ा होने पर बड़ा पूर्णांक बनाता है।

तो, अभी तक एक और प्रतिक्रिया:

from math import factorial

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

छोटी, सटीक और कुशल क्योंकि यह लोंगो के साथ चिपक कर अजगर के बड़े पूर्णांक से बचती है।

यह 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, परिणाम 1. होना चाहिए। यह कोड 0.
reyammer

अधिक सटीक रूप से, इसके range(n-r+1, n+1)बजाय होना चाहिए range(n-r,n+1)
पुनर्जन्म

1

यह बिलियन मेमोइज़ेशन डेकोरेटर का उपयोग करके @ किलरटी 2333 कोड है।

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) = तथ्य (30) / (तथ्य (7) * तथ्य (23)) = (30 * 29 * 28 * 27 * 26 * 25 * 24) / (1 * 2 * 3 * 4 * ५ * ६ * 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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.