Trình tạo Pythonic hiệu quả của chuỗi Fibonacci
Tôi đã tìm thấy câu hỏi này trong khi cố gắng để có được thế hệ Pythonic ngắn nhất của chuỗi này (sau đó nhận ra rằng tôi đã thấy một câu hỏi tương tự trong Đề xuất cải tiến Python ) và tôi không nhận thấy ai khác đưa ra giải pháp cụ thể của mình (mặc dù câu trả lời hàng đầu trở nên gần gũi, nhưng vẫn kém thanh lịch), vì vậy đây là, với các bình luận mô tả lần lặp đầu tiên, bởi vì tôi nghĩ rằng điều đó có thể giúp người đọc hiểu:
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)
và cách sử dụng:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
in:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Đối với mục đích phân bổ, gần đây tôi nhận thấy một triển khai tương tự trong tài liệu Python trên các mô-đun, thậm chí sử dụng các biến a
và b
bây giờ tôi nhớ lại đã thấy trước khi viết câu trả lời này. Nhưng tôi nghĩ câu trả lời này thể hiện cách sử dụng ngôn ngữ tốt hơn.)
Xác định đệ quy
Từ điển bách khoa toàn thư về chuỗi số nguyên xác định theo trình tự Fibonacci theo cách đệ quy là
F (n) = F (n-1) + F (n-2) với F (0) = 0 và F (1) = 1
Xác định rõ ràng điều này một cách đệ quy trong Python có thể được thực hiện như sau:
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
Nhưng biểu diễn chính xác của định nghĩa toán học này cực kỳ kém hiệu quả đối với các số lớn hơn 30, bởi vì mỗi số được tính toán cũng phải tính cho mọi số bên dưới nó. Bạn có thể chứng minh mức độ chậm của nó bằng cách sử dụng như sau:
for i in range(40):
print(i, rec_fib(i))
Ghi nhớ đệ quy cho hiệu quả
Nó có thể được ghi nhớ để cải thiện tốc độ (ví dụ này lợi dụng thực tế là một đối số từ khóa mặc định là cùng một đối tượng mỗi khi hàm được gọi, nhưng thông thường bạn sẽ không sử dụng đối số mặc định có thể thay đổi cho chính xác lý do này):
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
Bạn sẽ thấy phiên bản ghi nhớ nhanh hơn nhiều và sẽ nhanh chóng vượt quá độ sâu đệ quy tối đa của bạn trước khi bạn kịp nghĩ đến việc dậy uống cà phê. Bạn có thể thấy nó nhanh hơn bao nhiêu bằng cách thực hiện điều này:
for i in range(40):
print(i, mem_fib(i))
(Có vẻ như chúng ta chỉ có thể thực hiện các thao tác bên dưới, nhưng thực tế nó không cho phép chúng ta tận dụng bộ đệm, vì nó tự gọi trước khi setdefault được gọi.)
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
Trình tạo định nghĩa đệ quy:
Khi tôi đang học Haskell, tôi đã bắt gặp cách triển khai này trong Haskell:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Gần nhất tôi nghĩ rằng tôi có thể có được điều này trong Python tại thời điểm này là:
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
Điều này chứng tỏ điều đó:
[f for _, f in zip(range(999), fib())]
Nó chỉ có thể đi đến giới hạn đệ quy. Thông thường, 1000, trong khi phiên bản Haskell có thể lên tới 100 triệu, mặc dù nó sử dụng tất cả 8 GB bộ nhớ máy tính xách tay của tôi để làm như vậy:
> length $ take 100000000 fib
100000000
Sử dụng iterator để có được số thứ n Wikipedia
Một bình luận hỏi:
Câu hỏi cho hàm Fib () dựa trên iterator: nếu bạn muốn lấy số thứ n, ví dụ số sợi thứ 10 thì sao?
Tài liệu itertools có một công thức cho việc này:
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)
và bây giờ:
>>> nth(fib(), 10)
55