Ефективний пітонічний генератор послідовності Фібоначчі
Я знайшов це питання під час спроби отримати найкоротшу пітонічну генерацію цієї послідовності (пізніше зрозумівши, що я бачив подібне у пропозиції щодо розширення Python ), і я не помітив, щоб хто-небудь інший придумував моє конкретне рішення (хоча верхня відповідь наближається, але все ж менш елегантно), тож ось це, з коментарями, що описують першу ітерацію, тому що я думаю, що це може допомогти читачам зрозуміти:
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)
та використання:
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
# 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
Це демонструє це:
[f for _, f in zip(range(999), fib())]
Однак він може піднятися лише до межі рекурсії. Зазвичай 1000, тоді як версія Haskell може досягати 100 мільйонів, хоча для цього використовується 8 Гб пам'яті мого ноутбука:
> length $ take 100000000 fib
100000000
Споживаючи ітератор, щоб отримати номер n-го значення
Коментолог запитує:
Питання для функції Fib (), заснованої на ітераторі: що робити, якщо ви хочете отримати n-й, наприклад, 10-й номер фіб?
Документація 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