Statistiques: combinaisons en Python

j'ai besoin de calculer des combinatoires (nCr) en Python mais je ne trouve pas la fonction pour faire cela dans les bibliothèques math , numpy ou stat . Quelque chose comme une fonction du type:

comb = calculate_combinations(n, r)

j'ai besoin du nombre de combinaisons possibles, pas des combinaisons réelles, donc itertools.combinations ne m'intéresse pas.

enfin, je veux éviter d'utiliser des factoriels, car les nombres que je vais calculer les combinaisons pour peuvent obtenir trop gros et la factorielles sont va être monstrueux.

cela semble comme une question vraiment facile à répondre, mais je me noie dans des questions sur la génération de toutes les combinaisons réelles, qui n'est pas ce que je veux. :)

merci Beaucoup

95
demandé sur Dataman 2010-06-11 22:13:16

13 réponses

voir scipy.spécial.PEB (scipy.misc.peigne dans les anciennes versions de scipy). Quand exact est faux, il utilise la fonction gammaln pour obtenir une bonne précision sans prendre beaucoup de temps. Dans le cas précis, il renvoie une précision arbitraire entier, ce qui peut prendre beaucoup de temps à calculer.

95
répondu Jouni K. Seppänen 2017-12-04 17:24:58

pourquoi ne pas l'écrire vous-même? C'est une doublure ou un truc du genre:

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

essai-impression triangle de 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
>>> 

PS. modifié pour remplacer int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) avec int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1)) donc il ne sera pas err pour grand N /K

101
répondu Nas Banov 2013-09-17 01:13:08

une recherche rapide sur google code donne (il utilise la formule de @Mark Byers réponse ):

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() est 10 fois plus rapide (testé sur tous les couples 0 <= (N,k) < 1e3) que scipy.misc.comb() si vous avez besoin d'une réponse exacte.

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
44
répondu jfs 2017-05-23 11:46:49

si vous voulez des résultats exacts et vitesse, essayez gmpy -- gmpy.comb devrait faire exactement ce que vous demandez, et c'est assez rapide (bien sûr, comme gmpy l 'auteur original de l ' am biaisé;-).

40
répondu Alex Martelli 2010-06-11 21:15:40

si vous voulez un résultat exact, Utilisez sympy.binomial . Ça semble être la méthode la plus rapide, haut les mains.

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
23
répondu Jim Garrison 2013-11-23 12:56:36

une traduction littérale de la définition mathématique est tout à fait adéquate dans de nombreux cas (en se rappelant que Python utilisera automatiquement l'arithmétique des grands nombres):

from math import factorial

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

pour certaines entrées que j'ai testées (par exemple n=1000 R=500), c'était plus de 10 fois plus rapide que la doublure reduce suggérée dans une autre réponse (actuellement la plus élevée votée). D'un autre côté, il est exécuté par le snippit fourni par @J. F. Sebastian.

18
répondu Todd Owen 2013-11-26 06:25:15

Voici une autre alternative. Celui-ci a été écrit à l'origine en C++, donc il peut être rétroporté en C++ pour un entier de précision finie (par exemple __int64). L'avantage est (1) il implique seulement des opérations entières, et (2) il évite de gonfler la valeur entière en faisant des paires successives de multiplication et de division. J'ai testé le résultat avec le triangle Pascal de Nas Banov, il obtient la bonne réponse:

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

raison D'être: minimiser le nombre de multiplications et divisions, nous réécrivons l'expression comme

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

pour éviter le débordement de multiplication autant que possible, nous allons évaluer dans l'ordre STRICT suivant, de gauche à droite:

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

nous pouvons montrer que l'arithmétique entière appliquée dans cet ordre est exacte (c.-à-d. pas d'erreur de arrondi).

9
répondu Wirawan Purwanto 2012-08-24 19:29:45

en utilisant la programmation dynamique, la complexité temporelle est Θ (N*m) et la complexité spatiale Θ (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
répondu pantelis300 2014-09-12 17:30:10

si votre programme a une limite supérieure à n (dire n <= N ) et a besoin de calculer à plusieurs reprises nCr (de préférence pour > > N fois), en utilisant lru_cache peut vous donner un énorme coup de pouce de performance:

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)

construire le cache (ce qui est fait implicitement) prend jusqu'à O(N^2) temps. Tous les appels subséquents à nCr retourneront dans O(1) .

2
répondu yzn-pku 2017-04-30 14:41:45

la formule directe produit de grands nombres entiers quand n est plus grand que 20.

donc, encore une autre réponse:

from math import factorial

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

court, rapide et efficace.

1
répondu olivecoder 2015-12-04 11:46:38

c'est assez facile avec sympy.

import sympy

comb = sympy.binomial(n, r)
1
répondu Bobby 2016-07-17 18:21:07

c'est probablement aussi rapide que vous pouvez le faire en python pur pour des entrées raisonnablement grandes:

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
répondu Rabih Kodeih 2015-05-24 20:42:25

utilisant seulement bibliothèque standard distribuée avec Python :

import itertools

def nCk(n, k):
    return len(list(itertools.combinations(range(n), k)))
0
répondu MarianD 2016-11-10 22:29:34