Efficiente generatore pitonico della sequenza di Fibonacci
Ho trovato questa domanda mentre cercavo di ottenere la generazione Pythonic più breve di questa sequenza (rendendomi conto in seguito di averne visto una simile in una proposta di potenziamento di Python ) e non ho notato nessun altro trovare la mia soluzione specifica (sebbene la risposta migliore si avvicina, ma è ancora meno elegante), quindi eccolo qui, con commenti che descrivono la prima iterazione, perché penso che possa aiutare i lettori a capire:
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)
e utilizzo:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
stampe:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Ai fini dell'attribuzione, di recente ho notato un'implementazione simile nella documentazione di Python sui moduli, anche usando le variabili a
e b
, che ora ricordo di aver visto prima di scrivere questa risposta. Ma penso che questa risposta dimostri un uso migliore del linguaggio.)
Implementazione definita in modo ricorsivo
L' enciclopedia online delle sequenze di numeri interi definisce la sequenza di Fibonacci in modo ricorsivo
F (n) = F (n-1) + F (n-2) con F (0) = 0 e F (1) = 1
La definizione succinta in modo ricorsivo in Python può essere fatta come segue:
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
Ma questa esatta rappresentazione della definizione matematica è incredibilmente inefficiente per numeri molto più grandi di 30, perché ogni numero da calcolare deve anche calcolare per ogni numero sottostante. Puoi dimostrare quanto è lento utilizzando quanto segue:
for i in range(40):
print(i, rec_fib(i))
Ricorsione memorizzata per efficienza
Può essere memorizzato per migliorare la velocità (questo esempio sfrutta il fatto che un argomento di parola chiave predefinito è lo stesso oggetto ogni volta che viene chiamata la funzione, ma normalmente non useresti un argomento di default mutabile proprio per questo motivo):
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
Scoprirai che la versione memorizzata è molto più veloce e supererà rapidamente la massima profondità di ricorsione prima ancora che tu possa pensare di alzarti per un caffè. Puoi vedere quanto è più veloce visivamente facendo questo:
for i in range(40):
print(i, mem_fib(i))
(Può sembrare che possiamo semplicemente fare quanto segue, ma in realtà non ci consente di sfruttare la cache, perché si chiama da solo prima di chiamare setdefault.)
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
Generatore definito ricorsivamente:
Mentre stavo imparando Haskell, mi sono imbattuto in questa implementazione in Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Il più vicino che penso di poter ottenere in questo momento in Python è:
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
Questo lo dimostra:
[f for _, f in zip(range(999), fib())]
Tuttavia, può arrivare solo al limite di ricorsione. Di solito, 1000, mentre la versione di Haskell può arrivare a centinaia di milioni, anche se usa tutti gli 8 GB di memoria del mio laptop per farlo:
> length $ take 100000000 fib
100000000
Consumare l'iteratore per ottenere l'ennesimo numero di fibonacci
Un commentatore chiede:
Domanda per la funzione Fib () che si basa sull'iteratore: cosa succede se si desidera ottenere l'ennesimo, ad esempio il decimo numero di fib?
La documentazione di itertools ha una ricetta per questo:
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)
e adesso:
>>> nth(fib(), 10)
55