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
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.
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
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
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é;-).
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
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.
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).
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]
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)
.
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.
c'est assez facile avec sympy.
import sympy
comb = sympy.binomial(n, r)
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
utilisant seulement bibliothèque standard distribuée avec Python :
import itertools
def nCk(n, k):
return len(list(itertools.combinations(range(n), k)))