web-dev-qa-db-fra.com

Statistiques: combinaisons dans Python

J'ai besoin de calculer des combinatoires (nCr) dans Python mais je ne trouve pas la fonction pour le faire 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 factorielles, car les nombres pour lesquels je vais calculer les combinaisons peuvent devenir trop gros et que les factorielles vont être monstrueuses.

Cela semble être une question VRAIMENT facile à répondre, mais je suis submergé de questions sur la génération de toutes les combinaisons réelles, ce qui n’est pas ce que je veux.

107
Morlock

Voir scipy.special.comb (scipy.misc.comb dans les anciennes versions de scipy). Lorsque exact vaut False, il utilise la fonction gammaln pour obtenir une bonne précision sans prendre beaucoup de temps. Dans le cas exact, il retourne un entier de précision arbitraire, dont le calcul peut prendre beaucoup de temps.

105
Jouni K. Seppänen

Pourquoi ne pas l'écrire toi-même? C'est un one-liner ou tel:

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 - impression du 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 édité pour remplacer int(round(reduce(mul, (float(n-i)/(i+1) for i in range(k)), 1))) par int(reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1)) afin d'éviter les erreurs pour les gros N/K

107
Nas Banov

Une recherche rapide sur le code de Google donne (il utilise la formule de réponse de @ 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() est 10 fois plus rapide (testé sur toutes les paires 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
48
jfs

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

40
Alex Martelli

Si vous voulez un résultat exact, utilisez sympy.binomial . Cela semble être la méthode la plus rapide, haut la main.

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
27
Jim Garrison

Une traduction littérale de la définition mathématique est tout à fait adéquate dans de nombreux cas (en gardant à l'esprit 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 = 1 000 r = 500), cela était plus de 10 fois plus rapide que la doublure reduce proposée dans une autre réponse (actuellement la plus votée). En revanche, il est dépassé par l'extrait fourni par @ J.F. Sebastian.

20
Todd Owen

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

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

Justification: pour minimiser le nombre de multiplications et de divisions, nous réécrivons l’expression comme suit.

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

Pour éviter autant que possible le dépassement de multiplication, nous évaluerons 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'arithmatique entière utilisée dans cet ordre est exacte (c'est-à-dire sans erreur d'arrondi).

9
Wirawan Purwanto

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]
5
pantelis300

Si votre programme a une limite supérieure à n (disons n <= N) Et doit calculer plusieurs fois nCr (de préférence pour >> N fois), utilisez lru_cache peut vous donner un énorme gain 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)

La construction du cache (de manière implicite) prend jusqu'à O(N^2) temps. Tous les appels ultérieurs à nCr renverront dans O(1).

3
yzn-pku

Vous pouvez écrire 2 fonctions simples qui s'avèrent être environ 5 à 8 fois plus rapides qu'avec scipy.special.comb . En fait, vous n'avez pas besoin d'importer de paquet supplémentaire, et la fonction est assez facilement lisible. L'astuce consiste à utiliser la mémorisation pour stocker les valeurs calculées précédemment et à l'aide de la définition de nCr

# create a memoization dict
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*fact(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))

Si on compare les temps

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

La formule directe produit de grands entiers lorsque n est supérieur à 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
olivecoder

Ceci est le code @ killerT2333 utilisant le décorateur de mémoization intégré.

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

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

import itertools

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

C'est assez facile avec sympy.

import sympy

comb = sympy.binomial(n, r)
1
Bobby

À partir de Python 3.8, La bibliothèque standard inclut désormais la fonction math.comb pour calculer le coefficient binomial:

math.comb (n, k)

qui est le nombre de façons de choisir k éléments parmi n éléments sans répétition
n! / (k! (n - k)!):

import math
math.comb(10, 5) # 252
0
Xavier Guihot

Cette fonction est très optimisée.

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
0

C'est probablement aussi rapide que vous pouvez le faire en pure python 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
Rabih Kodeih