斐波那契数列的高效Python生成器
我在尝试获得该序列的最短Pythonic版本时发现了这个问题(后来意识到我在Python Enhancement Proposal中看到了类似的问题),但我还没有注意到其他人提出了我的具体解决方案(尽管最重要的答案)变得更接近,但仍然不那么优雅),因此,这里有描述第一次迭代的注释,因为我认为这可以帮助读者理解:
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版本可以达到1亿,尽管它使用了笔记本电脑的全部8 GB内存:
> 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