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.
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.
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
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
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é; -).
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
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.
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).
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
(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)
.
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
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.
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))
Utiliser seulement bibliothèque standard distribuée avec Python:
import itertools
def nCk(n, k):
return len(list(itertools.combinations(range(n), k)))
C'est assez facile avec sympy.
import sympy
comb = sympy.binomial(n, r)
À 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étitionn! / (k! (n - k)!)
:
import math
math.comb(10, 5) # 252
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
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