フィボナッチ数列の効率的なPythonicジェネレーター
私はこのシーケンスの最短のPythonic生成を取得しようとしたときにこの質問を見つけました(後で、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のバージョンは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