Эффективный питонический генератор последовательности Фибоначчи
Я нашел этот вопрос, пытаясь получить самую короткую Pythonic-генерацию этой последовательности (позже я понял, что видел подобное в предложении по расширению Python ), и я не заметил, чтобы кто-то еще предлагал мое конкретное решение (хотя главный ответ приближается, но все же менее элегантно), так что вот оно, с комментариями, описывающими первую итерацию, потому что я думаю, что это может помочь читателям понять:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
и использование:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
печатает:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Для целей атрибуции я недавно заметил аналогичную реализацию в документации Python по модулям, даже с использованием переменных a
и b
, которые, как я припоминаю, видел перед написанием этого ответа. Но я думаю, что этот ответ демонстрирует лучшее использование языка.)
Рекурсивно определенная реализация
Интернет Энциклопедия целочисленных последовательностей определяет последовательность Фибоначчи рекурсивно
F (n) = F (n-1) + F (n-2) с F (0) = 0 и F (1) = 1
Кратко определить это рекурсивно в Python можно следующим образом:
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
Но это точное представление математического определения невероятно неэффективно для чисел, намного превышающих 30, потому что каждое вычисляемое число должно также вычисляться для каждого числа ниже него. Вы можете продемонстрировать, насколько он медленный, используя следующее:
for i in range(40):
print(i, rec_fib(i))
Мемоизированная рекурсия для эффективности
Его можно запоминать для повышения скорости (в этом примере используется тот факт, что аргумент ключевого слова по умолчанию является одним и тем же объектом каждый раз при вызове функции, но обычно вы не будете использовать изменяемый аргумент по умолчанию именно по этой причине):
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
Вы обнаружите, что мемоизированная версия работает намного быстрее и быстро превысит вашу максимальную глубину рекурсии, прежде чем вы даже сможете подумать о том, чтобы встать для кофе. Вы можете визуально увидеть, насколько это быстрее, сделав следующее:
for i in range(40):
print(i, mem_fib(i))
(Может показаться, что мы можем просто сделать следующее, но на самом деле это не позволяет нам воспользоваться кешем, потому что он вызывает себя до вызова 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
Рекурсивно определенный генератор:
Изучая Haskell, я наткнулся на эту реализацию в Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
На данный момент я думаю, что ближе всего к этому в Python я могу подойти:
from itertools import tee
def fib():
yield 0
yield 1
f, tf = tee(fib())
next(tf)
for a, b in zip(f, tf):
yield a + b
Это демонстрирует:
[f for _, f in zip(range(999), fib())]
Однако он может доходить только до предела рекурсии. Обычно 1000, тогда как версия Haskell может доходить до сотен миллионов, хотя для этого используются все 8 ГБ памяти моего ноутбука:
> length $ take 100000000 fib
100000000
Использование итератора для получения n-го числа Фибоначчи
Комментатор спрашивает:
Вопрос к функции Fib (), основанной на итераторе: что делать, если вы хотите получить n-е, например 10-е число fib?
В документации itertools есть рецепт для этого:
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)
и сейчас:
>>> nth(fib(), 10)
55