Factoriel:
Quand vous avez besoin de calculer n!, il existe plusieurs solutions:
- La solution « rapide »: qui utilise une boucle ou une fonction récursive:
def factorial_for(n): r = 1 for i in range(2, n + 1): r *= i return(r)
def factorial_rec(n): if n > 1: return(n * factorial_rec(n - 1)) else: return(1)
Ici, la multiplication séquentielle de chaque nombre va générer un nombre très grand très rapidement, ce qui n’est pas optimum. En effet, les ordinateurs sont plus rapides quand ils multiplient des petits nombres (120×30240) par rapport à la même multiplication avec un grand nombre (362880×10).
- La solution « intelligente »: Ici, l’idée est de séparer une séquence de nombres à multiplier en deux par le milieu, de façon récursive.
10! = 1x2x3x4x5x6x7x8x9 x 10 = 362880 x 10 (rush solution) = 1x2x3x4x5 x 6x7x8x9x10 = 120 x 30240 (1st split) = 1x2x3 x 4×5 x 6x7x8 x 9×10 = 6 x 20 x 336 x 90 (2nd split) def range_prod(lo, hi): if lo + 1 < hi: mid = (hi + lo) // 2 return(range_prod(lo, mid) * range_prod(mid + 1, hi)) if lo == hi: return(lo) return(lo * hi)
def factorial_tree(n): if n < 2: return(1) return(range_prod(1, n))
- La solution « paresseuse » ou « très intelligente » (tout dépend du point de vue): consiste à calculer une estimation du factoriel avec l’approximation de Stirling.
import math def stirling(n): return(math.sqrt(2 * math.pi * n) * (n / math.e) ** n)
Cette implémentation est limitée à n<170. Toutefois, comme vous pouvez le constater plus bas, avec n=170, le résultat est énorme.
Avec ce genre de gros nombre, nous préférons de toute façon travailler avec les log factoriels.In [01]: stirling(170) Out[01]: 7.253858934542908e+306 In [02]: stirling(171) Out[02]: inf
Log Factorial:
Pour les deux premières solutions, vous n’avez qu’à ajouter math.log() (en faisant import math
d’abord) :
|
|
def log_fact_for(n):
return(math.log(factorial_for(n)))
Le log factoriel avec l’approximation de Stirling est implémentée comme ceci :
def log_stirling(n):
fn = float(n)
fn = n * math.log(fn) + math.log(2 * math.pi * fn) / 2 - fn + \
(fn ** -1) / 12 - (fn ** -3) / 360 + (fn ** -5) / 1260 - \
(fn ** -7) / 1680 + (fn ** -9) / 1188
return(fn)
Note:
Dans le cas où vous avez de multiples log factoriels à calculer, il peut être utile de sauvegarder et réutiliser des résultats précédents et ainsi gagner du temps (par exemple, 10! = 6!x7x8x9x10) comme ceci :
def log_fact_exact_saved(n, value_saved):
select = max((i for i in value_saved.keys() if i <= n))
if select != n:
value_saved[n] = range_prod(select + 1, n) * value_saved[select]
return(math.log(value_saved[n]))
def log_fact_approx_saved(n, value_saved):
if n not in value_saved:
value_saved[n] = log_stirling(n)
return(value_saved[n])
Test:
Nous pouvons maintenant tester et comparer chacune de ces solutions en utilisant les librairies math
et scipy
pour voir laquelle est la plus rapide :
import sys
sys.setrecursionlimit(1000000) // increase the depth
import math
import scipy.special
def test_saved(saved, approx):
value = [10, 50, 100, 1000, 1000, 2000, 5000, 5000, 5000,
5000, 10000, 11000, 11000, 11000, 20000, 20000]
if approx:
if saved:
value_saved = {1: 1}
for num in value:
log_fact_approx_saved(num, value_saved)
else:
for num in value:
log_stirling(num)
else:
if saved:
value_saved = {1: 1}
for num in value:
log_fact_exact_saved(num, value_saved)
else:
for num in value:
log_fact_tree(num)
def log_fact_math(n):
return(math.log(math.factorial(n)))
def log_fact_scipy(n, exact=False):
return(math.log(scipy.special.factorial(n, exact)))
import timeit
num = 10000
rep = 200
print("rec:" + str(timeit.timeit("log_fact_rec(" + str(num) + ")",
setup="from __main__ import log_fact_rec", number=rep)))
print("for:" + str(timeit.timeit("log_fact_for(" + str(num) + ")",
setup="from __main__ import log_fact_for", number=rep)))
print("scipy exact:" + str(timeit.timeit("log_fact_scipy(" + str(num) + ", True)",
setup="from __main__ import log_fact_scipy", number=rep)))
print("math:" + str(timeit.timeit("log_fact_math(" + str(num) + ")",
setup="from __main__ import log_fact_math", number=rep)))
print("tree:" + str(timeit.timeit("log_fact_tree(" + str(num) + ")",
setup="from __main__ import log_fact_tree", number=rep)))
print("scipy approx:" + str(timeit.timeit("log_fact_scipy(" + str(num) + ")",
setup="from __main__ import log_fact_scipy", number=rep)))
print("log_stirling:" + str(timeit.timeit("log_stirling(" + str(num) + ")",
setup="from __main__ import log_stirling", number=rep)))
print("value log(100!) tree:" + str(log_fact_tree(100)))
print("value log(100!) scipy approx:" + str(log_fact_scipy(100)))
print("value log(100!) log_stirling:" + str(log_stirling(100)))
print("severals factorial:" + str(timeit.timeit("test_saved(False, False)",
setup="from __main__ import test_saved", number=rep)))
print("severals factorial saved:" + str(timeit.timeit("test_saved(True, False)",
setup="from __main__ import test_saved", number=rep)))
print("severals factorial approx:" + str(timeit.timeit("test_saved(False, True)",
setup="from __main__ import test_saved", number=rep)))
print("severals factorial approx saved:" + str(timeit.timeit("test_saved(True, True)",
setup="from __main__ import test_saved", number=rep)))
En utilisant python 2.7.6
et scipy 0.17.0
, nous avons :
rec:4.52898597717 for:4.09089803696 scipy exact:4.08975815773 math:4.02955198288 tree:1.2138531208 |
Nous pouvons voir que la solution « intelligente » est la plus rapide,
même lorsque comparée à |
scipy approx:0.00166201591492 log_stirling:0.000301837921143 |
Encore ici, scipy est le plus lent |
value log(100!) tree:363.739375556 value log(100!) scipy approx:363.739375556 value log(100!) log_stirling:363.739375556 |
Dans ce cas-ci, toutes les méthodes sont équivalentes pour n>=100 |
several factorials:14.3231370449 several factorials saved:3.51164102554 several factorials approx:0.00481510162354 several factorials approx saved:0.00360107421875 |
Sauvegarder des résultats et les réutiliser peut sauver beaucoup de temps,
mais prendra plus de mémoire. |
J’espère que cet article vous aidera à choisir la meilleure solution (adaptée à vos besoins!) pour calculer des nombres factoriels et log factoriels.
Laisser un commentaire