Generador pitónico eficiente de la secuencia de Fibonacci
Encontré esta pregunta al intentar obtener la generación Pythonic más corta de esta secuencia (más tarde me di cuenta de que había visto una similar en una Propuesta de Mejora de Python ), y no he notado que nadie más haya encontrado mi solución específica (aunque la respuesta principal se acerca, pero aún menos elegante), así que aquí está, con comentarios que describen la primera iteración, porque creo que eso puede ayudar a los lectores a comprender:
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)
y uso:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
huellas dactilares:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Para fines de atribución, recientemente noté una implementación similar en la documentación de Python en los módulos, incluso usando las variables a
y b
, que ahora recuerdo haber visto antes de escribir esta respuesta. Pero creo que esta respuesta demuestra un mejor uso del lenguaje).
Implementación definida recursivamente
La Enciclopedia en línea de secuencias enteras define la secuencia de Fibonacci recursivamente como
F (n) = F (n-1) + F (n-2) con F (0) = 0 y F (1) = 1
Definir sucintamente esto de forma recursiva en Python se puede hacer de la siguiente manera:
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
Pero esta representación exacta de la definición matemática es increíblemente ineficiente para números mucho mayores que 30, porque cada número que se calcula también debe calcular para cada número debajo de él. Puede demostrar lo lento que es utilizando lo siguiente:
for i in range(40):
print(i, rec_fib(i))
Recursión memorable para la eficiencia
Se puede memorizar para mejorar la velocidad (este ejemplo aprovecha el hecho de que un argumento de palabra clave predeterminado es el mismo objeto cada vez que se llama a la función, pero normalmente no usaría un argumento predeterminado mutable por exactamente este 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
Descubrirá que la versión memorable es mucho más rápida y superará rápidamente su profundidad máxima de recursión antes de que pueda siquiera pensar en levantarse para tomar un café. Puede ver cuánto más rápido es visualmente haciendo esto:
for i in range(40):
print(i, mem_fib(i))
(Puede parecer que podemos hacer lo siguiente, pero en realidad no nos permite aprovechar el caché, porque se llama a sí mismo antes de llamar a 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
Generador definido recursivamente:
Como he estado aprendiendo Haskell, me encontré con esta implementación en Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Lo más cerca que creo que puedo llegar a esto en Python en este momento es:
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
Esto lo demuestra:
[f for _, f in zip(range(999), fib())]
Sin embargo, solo puede subir hasta el límite de recurrencia. Por lo general, 1000, mientras que la versión de Haskell puede llegar a los cientos de millones, aunque utiliza los 8 GB de memoria de mi computadora portátil para hacerlo:
> length $ take 100000000 fib
100000000
Consumir el iterador para obtener el enésimo número de Fibonacci
Un comentarista pregunta:
Pregunta para la función Fib () que se basa en el iterador: ¿qué sucede si desea obtener el enésimo, por ejemplo, el décimo número de fib?
La documentación de itertools tiene una receta para esto:
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)
y ahora:
>>> nth(fib(), 10)
55