Effizienter Pythonic-Generator der Fibonacci-Sequenz
Ich habe diese Frage gefunden, als ich versucht habe, die kürzeste Pythonic-Generation dieser Sequenz zu erhalten (später wurde mir klar, dass ich eine ähnliche in einem Python-Verbesserungsvorschlag gesehen hatte ), und ich habe nicht bemerkt, dass jemand anderes meine spezifische Lösung gefunden hat (obwohl die beste Antwort kommt näher, aber immer noch weniger elegant), also hier mit Kommentaren, die die erste Iteration beschreiben, weil ich denke, dass dies den Lesern helfen kann, Folgendes zu verstehen:
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)
und Verwendung:
for index, fibonacci_number in zip(range(10), fib()):
print('{i:3}: {f:3}'.format(i=index, f=fibonacci_number))
Drucke:
0: 0
1: 1
2: 1
3: 2
4: 3
5: 5
6: 8
7: 13
8: 21
9: 34
10: 55
(Zu Attributionszwecken habe ich kürzlich eine ähnliche Implementierung in der Python-Dokumentation zu Modulen festgestellt , sogar unter Verwendung der Variablen a
und b
, die ich jetzt vor dem Schreiben dieser Antwort gesehen habe. Ich denke jedoch, dass diese Antwort eine bessere Verwendung der Sprache demonstriert.)
Rekursiv definierte Implementierung
Die Online-Enzyklopädie ganzzahliger Sequenzen definiert die Fibonacci-Sequenz rekursiv als
F (n) = F (n-1) + F (n-2) mit F (0) = 0 und F (1) = 1
Die prägnante Definition in Python kann wie folgt erfolgen:
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
Diese genaue Darstellung der mathematischen Definition ist jedoch für Zahlen, die viel größer als 30 sind, unglaublich ineffizient, da jede zu berechnende Zahl auch für jede darunter liegende Zahl berechnet werden muss. Sie können anhand der folgenden Methoden demonstrieren, wie langsam es ist:
for i in range(40):
print(i, rec_fib(i))
Gespeicherte Rekursion für Effizienz
Es kann zur Verbesserung der Geschwindigkeit gespeichert werden (in diesem Beispiel wird die Tatsache ausgenutzt, dass ein Standardschlüsselwortargument bei jedem Aufruf der Funktion dasselbe Objekt ist, aber normalerweise würden Sie aus genau diesem Grund kein veränderbares Standardargument verwenden):
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
Sie werden feststellen, dass die gespeicherte Version viel schneller ist und Ihre maximale Rekursionstiefe schnell überschreitet, bevor Sie überhaupt daran denken können, auf einen Kaffee aufzustehen. Sie können sehen, wie viel schneller es visuell ist, indem Sie dies tun:
for i in range(40):
print(i, mem_fib(i))
(Es mag so aussehen, als könnten wir nur das Folgende tun, aber es lässt uns den Cache nicht nutzen, da er sich selbst aufruft, bevor setdefault aufgerufen wird.)
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
Rekursiv definierter Generator:
Als ich Haskell gelernt habe, bin ich in Haskell auf diese Implementierung gestoßen:
fib@(0:tfib) = 0:1: zipWith (+) fib tfib
Das, was ich momentan in Python am ehesten erreichen kann, ist:
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
Dies zeigt es:
[f for _, f in zip(range(999), fib())]
Es kann jedoch nur bis zur Rekursionsgrenze gehen. Normalerweise 1000, während die Haskell-Version bis zu 100 Millionen erreichen kann, obwohl sie alle 8 GB des Speichers meines Laptops verwendet, um dies zu tun:
> length $ take 100000000 fib
100000000
Den Iterator verbrauchen, um die n-te Fibonacci-Zahl zu erhalten
Ein Kommentator fragt:
Frage für die Fib () -Funktion, die auf dem Iterator basiert: Was ist, wenn Sie die n-te, zum Beispiel die 10. Fib-Nummer erhalten möchten?
Die itertools-Dokumentation enthält ein Rezept dafür:
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)
und nun:
>>> nth(fib(), 10)
55