web-dev-qa-db-fra.com

compter les combinaisons et les permutations efficacement

J'ai un code pour compter les permutations et les combinaisons, et j'essaie de le rendre plus efficace pour les grands nombres.

J'ai trouvé un meilleur algorithme pour les permutations qui évite les grands résultats intermédiaires, mais je pense toujours que je peux faire mieux pour les combinaisons.

Jusqu'ici, j'ai mis un cas particulier pour refléter la symétrie de nCr, mais j'aimerais quand même trouver un meilleur algorithme qui évite l'appel à factorial (r), qui est un résultat intermédiaire inutilement grand. Sans cette optimisation, le dernier doctest prend trop de temps à essayer de calculer factoriel (99000).

Quelqu'un peut-il suggérer un moyen plus efficace de compter les combinaisons?

from math import factorial

def product(iterable):
    prod = 1
    for n in iterable:
        prod *= n
    return prod

def npr(n, r):
    """
    Calculate the number of ordered permutations of r items taken from a
    population of size n.

    >>> npr(3, 2)
    6
    >>> npr(100, 20)
    1303995018204712451095685346159820800000
    """
    assert 0 <= r <= n
    return product(range(n - r + 1, n + 1))

def ncr(n, r):
    """
    Calculate the number of unordered combinations of r items taken from a
    population of size n.

    >>> ncr(3, 2)
    3
    >>> ncr(100, 20)
    535983370403809682970
    >>> ncr(100000, 1000) == ncr(100000, 99000)
    True
    """
    assert 0 <= r <= n
    if r > n // 2:
        r = n - r
    return npr(n, r) // factorial(r)
33
Christian Oudard

si n n'est pas loin de r, alors utiliser la définition récursive de la combinaison est probablement préférable, puisque xC0 == 1, vous n'aurez que quelques itérations:

La définition récursive pertinente est la suivante:

nCr = (n-1) C (r-1) * n/r

Ceci peut être bien calculé en utilisant la récursion avec la liste suivante:

[(n - r, 0), (n - r + 1, 1), (n - r + 2, 2), ..., (n - 1, r - 1), (n, r)]

qui est bien sûr facilement généré en Python (nous omettons la première entrée depuis nC0 = 1) par izip(xrange(n - r + 1, n+1), xrange(1, r+1)) Notez que cela suppose que r <= n vous devez le vérifier et les échanger s’ils ne le sont pas. Aussi pour optimiser l'utilisation si r <n/2 alors r = n - r.

Maintenant, il suffit d’appliquer l’étape de récursivité en utilisant la récursion de la queue avec réduire. Nous commençons par 1 puisque nC0 vaut 1, puis multiplions la valeur actuelle par la prochaine entrée de la liste, comme ci-dessous.

from itertools import izip

reduce(lambda x, y: x * y[0] / y[1], izip(xrange(n - r + 1, n+1), xrange(1, r+1)), 1)
22
wich

Deux suggestions assez simples:

  1. Pour éviter tout débordement, faites tout dans l’espace journal. Utilisez le fait que log (a * b) = log (a) + log (b) et log (a/b) = log (a) - log (b). Cela facilite le travail avec de très grandes factorielles: log (n!/M!) = Log (n!) - log (m!), Etc.

  2. Utilisez la fonction gamma au lieu de factorielle. Vous pouvez en trouver un dans scipy.stats.loggamma. C'est un moyen beaucoup plus efficace de calculer les facteurs de logarithme que la sommation directe. loggamma(n) == log(factorial(n - 1)), et de la même manière, gamma(n) == factorial(n - 1).

16
dsimcha

Il y a une fonction pour cela dans scipy qui n'a pas encore été mentionnée: scipy.special.comb . Cela semble efficace si on se base sur des résultats rapides pour votre doctest (~ 0,004 seconde pour comb(100000, 1000, 1) == comb(100000, 99000, 1)).

[Bien que cette question spécifique semble concerner les algorithmes, la question existe-t-il une fonction mathématique en python est marquée comme une copie de celle-ci ...]

6
dshepherd

Si vous n'avez pas besoin d'une solution pure-python, gmpy2 pourrait vous aider (gmpy2.comb est très rapide).

6
Alex Martelli

Si votre problème n'exige pas de connaître le nombre exact de permutations ou de combinaisons, vous pouvez utiliser approximation de Stirling pour la factorielle.

Cela conduirait à coder comme ceci:

import math

def stirling(n):
    # http://en.wikipedia.org/wiki/Stirling%27s_approximation
    return math.sqrt(2*math.pi*n)*(n/math.e)**n

def npr(n,r):
    return (stirling(n)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(n-r))

def ncr(n,r):    
    return (stirling(n)/stirling(r)/stirling(n-r) if n>20 else
            math.factorial(n)/math.factorial(r)/math.factorial(n-r))

print(npr(3,2))
# 6
print(npr(100,20))
# 1.30426670868e+39
print(ncr(3,2))
# 3
print(ncr(100,20))
# 5.38333246453e+20
3
unutbu

Si vous calculez N choisissez K (ce que je pense que vous faites avec ncr), il existe une solution de programmation dynamique qui peut être beaucoup plus rapide. Cela évitera les factorielles et vous pourrez conserver la table si vous le souhaitez pour une utilisation ultérieure.

Voici un lien pédagogique pour cela:

http://www.csc.liv.ac.uk/~ped/teachadmin/algor/dyprog.html

Je ne sais pas comment mieux résoudre votre premier problème, pardon.

Edit: Voici la maquette. Il y a des erreurs assez hilarantes, alors il peut certainement y avoir plus de nettoyage.

import sys
n = int(sys.argv[1])+2#100
k = int(sys.argv[2])+1#20
table = [[0]*(n+2)]*(n+2)

for i in range(1,n):
    table[i][i] = 1
for i in range(1,n):
    for j in range(1,n-i):
        x = i+j
        if j == 1: table[x][j] = 1
        else: table[x][j] = table[x-1][j-1] + table[x-1][j]

print table[n][k]
2
agorenst
from scipy import misc
misc.comb(n, k)

devrait vous permettre de compter les combinaisons

1
Divyansh

Solution plus efficace pour nCr - en termes d'espace et de précision. 

L'intermédiaire (res) est garanti pour toujours être int et jamais plus grand que le résultat. La complexité de l'espace est O(1) (pas de liste, pas de zip, pas de pile), la complexité temporelle est O(r) - exactement r multiplications et divisions.

def ncr(n, r):
    r = min(r, n-r)
    if r == 0: return 1
    res = 1
    for k in range(1,r+1):
        res = res*(n-k+1)/k
    return res
1
ZXX

Pour N choisissez K, vous pouvez utiliser le triangle de Pascals. Fondamentalement, vous devez conserver un tableau de taille N pour calculer toutes les N valeurs de K choisies. Seuls les ajouts seraient nécessaires.

0
Richie

Utiliser xrange() au lieu de range() accélérera un peu les choses car aucune liste intermédiaire n'est créée, remplie, itérée, puis détruite. En outre, reduce() avec operator.mul.

0

Vous pouvez entrer deux entiers et importer une bibliothèque mathématique pour trouver la factorielle, puis appliquer la formule nCr.

import math
n,r=[int(_)for _ in raw_input().split()]
f=math.factorial
print f(n)/f(r)/f(n-r)
0
Gaurav Jain
from numpy import prod

def nCr(n,r):
    numerator = range(n, max(n-r,r),-1)
    denominator = range(1, min(n-r,r) +1,1)
    return int(prod(numerator)/prod(denominator))
0
Kumar