Générateur pythonique efficace de la séquence de Fibonacci
J'ai trouvé cette question en essayant d'obtenir la génération pythonique la plus courte de cette séquence (réalisant plus tard que j'en avais vu une similaire dans une proposition d'amélioration de Python ), et je n'ai remarqué personne d'autre proposer ma solution spécifique (bien que la première réponse se rapproche, mais encore moins élégant), alors le voici, avec des commentaires décrivant la première itération, car je pense que cela peut aider les lecteurs à comprendre:
def fib():
a, b = 0, 1
while True: # First iteration:
yield a # yield 0 to start with and then
a, b = b, a + b # a will now be 1, and b will also be 1, (0 + 1)
et utilisation:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
imprime:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(À des fins d'attribution, j'ai récemment remarqué une implémentation similaire dans la documentation Python sur les modules, même en utilisant les variables a
et b
, que je me souviens maintenant d'avoir vu avant d'écrire cette réponse. Mais je pense que cette réponse démontre une meilleure utilisation du langage.)
Implémentation définie de manière récursive
L' Encyclopédie en ligne des séquences entières définit la séquence de Fibonacci de manière récursive comme
F (n) = F (n-1) + F (n-2) avec F (0) = 0 et F (1) = 1
La définition succincte de cela de manière récursive en Python peut être effectuée comme suit:
def rec_fib(n):
'''inefficient recursive function as defined, returns Fibonacci number'''
if n > 1:
return rec_fib(n-1) + rec_fib(n-2)
return n
Mais cette représentation exacte de la définition mathématique est incroyablement inefficace pour les nombres bien supérieurs à 30, car chaque nombre en cours de calcul doit également calculer pour chaque nombre en dessous. Vous pouvez démontrer sa lenteur en utilisant ce qui suit:
for i in range(40):
print(i, rec_fib(i))
Récursivité mémorisée pour plus d'efficacité
Il peut être mémorisé pour améliorer la vitesse (cet exemple tire parti du fait qu'un argument de mot-clé par défaut est le même objet à chaque fois que la fonction est appelée, mais normalement vous n'utiliseriez pas un argument par défaut mutable pour exactement cette raison):
def mem_fib(n, _cache={}):
'''efficiently memoized recursive function, returns a Fibonacci number'''
if n in _cache:
return _cache[n]
elif n > 1:
return _cache.setdefault(n, mem_fib(n-1) + mem_fib(n-2))
return n
Vous constaterez que la version mémorisée est beaucoup plus rapide et dépassera rapidement votre profondeur de récursivité maximale avant même de pouvoir penser à vous lever pour prendre un café. Vous pouvez voir à quel point il est visuellement plus rapide en faisant ceci:
for i in range(40):
print(i, mem_fib(i))
(Il peut sembler que nous pouvons simplement faire ce qui suit, mais cela ne nous permet pas de profiter du cache, car il s'appelle lui-même avant que setdefault ne soit appelé.)
def mem_fib(n, _cache={}):
'''don't do this'''
if n > 1:
return _cache.setdefault(n, mem_fib(n-1) + mem_fib(n-2))
return n
Générateur défini récursivement:
En apprenant Haskell, je suis tombé sur cette implémentation dans Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Le plus proche que je pense pouvoir arriver à cela en Python pour le moment est:
from itertools import tee
def fib():
yield 0
yield 1
# tee required, else with two fib()'s algorithm becomes quadratic
f, tf = tee(fib())
next(tf)
for a, b in zip(f, tf):
yield a + b
Cela le démontre:
[f for _, f in zip(range(999), fib())]
Il ne peut cependant aller que jusqu'à la limite de récursivité. Habituellement, 1000, alors que la version Haskell peut aller jusqu'à des centaines de millions, bien qu'elle utilise les 8 Go de mémoire de mon ordinateur portable pour ce faire:
> length $ take 100000000 fib
100000000
Consommer l'itérateur pour obtenir le nième numéro de fibonacci
Un commentateur demande:
Question pour la fonction Fib () qui est basée sur l'itérateur: que faire si vous voulez obtenir le nième, par exemple le 10e numéro de fib?
La documentation itertools a une recette pour cela:
from itertools import islice
def nth(iterable, n, default=None):
"Returns the nth item or a default value"
return next(islice(iterable, n, None), default)
et maintenant:
>>> nth(fib(), 10)
55