Generator Pythonic yang efisien dari deret Fibonacci
Saya menemukan pertanyaan ini ketika mencoba untuk mendapatkan generasi Pythonic terpendek dari urutan ini (kemudian menyadari bahwa saya telah melihat yang serupa dalam Proposal Peningkatan Python ), dan saya belum melihat ada orang lain yang datang dengan solusi spesifik saya (walaupun jawaban teratas semakin dekat, tetapi masih kurang elegan), jadi ini dia, dengan komentar yang menggambarkan iterasi pertama, karena saya pikir itu dapat membantu pembaca memahami:
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)
dan penggunaan:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
cetakan:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Untuk tujuan atribusi, saya baru-baru ini memperhatikan implementasi yang serupa dalam dokumentasi Python pada modul, bahkan menggunakan variabel a
dan b
, yang sekarang saya ingat pernah melihat sebelum menulis jawaban ini. Tapi saya pikir jawaban ini menunjukkan penggunaan bahasa yang lebih baik.)
Implementasi yang didefinisikan secara rekursif
The online Encyclopedia of Integer Urutan mendefinisikan Fibonacci Sequence rekursif sebagai
F (n) = F (n-1) + F (n-2) dengan F (0) = 0 dan F (1) = 1
Mendefinisikan secara ringkas ini secara Python dapat dilakukan sebagai berikut:
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
Tetapi representasi pasti dari definisi matematika ini sangat tidak efisien untuk angka-angka yang jauh lebih besar dari 30, karena setiap angka yang dihitung juga harus menghitung untuk setiap angka di bawahnya. Anda dapat menunjukkan seberapa lambatnya dengan menggunakan yang berikut:
for i in range(40):
print(i, rec_fib(i))
Rekursi memo untuk efisiensi
Ini dapat di-memoise untuk meningkatkan kecepatan (contoh ini mengambil keuntungan dari fakta bahwa argumen kata kunci default adalah objek yang sama setiap kali fungsi dipanggil, tetapi biasanya Anda tidak akan menggunakan argumen default yang bisa berubah-ubah untuk alasan ini):
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
Anda akan menemukan versi memoized jauh lebih cepat, dan akan dengan cepat melebihi kedalaman rekursi maksimum Anda bahkan sebelum Anda berpikir untuk bangun untuk minum kopi. Anda dapat melihat seberapa cepat secara visual dengan melakukan ini:
for i in range(40):
print(i, mem_fib(i))
(Sepertinya kita bisa melakukan yang di bawah ini, tetapi sebenarnya tidak membiarkan kita mengambil keuntungan dari cache, karena ia memanggil dirinya sendiri sebelum setdefault dipanggil.)
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
Generator yang didefinisikan secara rekursif:
Karena saya telah belajar Haskell, saya menemukan implementasi ini di Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Yang paling dekat saya pikir bisa saya dapatkan dengan Python saat ini adalah:
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
Ini menunjukkannya:
[f for _, f in zip(range(999), fib())]
Namun, itu hanya bisa mencapai batas rekursi. Biasanya, 1000, sedangkan versi Haskell dapat mencapai 100-an juta, meskipun menggunakan semua 8 GB memori laptop saya untuk melakukannya:
> length $ take 100000000 fib
100000000
Mengkonsumsi iterator untuk mendapatkan nomor fibonacci ke-n
Seorang komentator bertanya:
Pertanyaan untuk fungsi Fib () yang didasarkan pada iterator: bagaimana jika Anda ingin mendapatkan yang ke-n, misalnya nomor ke-10?
Dokumentasi itertools memiliki resep untuk ini:
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)
dan sekarang:
>>> nth(fib(), 10)
55