Um zu zeigen, wie Sie itertools
Rezepte kombinieren können , erweitere ich das pairwise
Rezept mit dem Rezept so direkt wie möglich wieder in window
das consume
Rezept:
def consume(iterator, n):
"Advance the iterator n-steps ahead. If n is none, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
def window(iterable, n=2):
"s -> (s0, ...,s(n-1)), (s1, ...,sn), (s2, ..., s(n+1)), ..."
iters = tee(iterable, n)
# Could use enumerate(islice(iters, 1, None), 1) to avoid consume(it, 0), but that's
# slower for larger window sizes, while saving only small fixed "noop" cost
for i, it in enumerate(iters):
consume(it, i)
return zip(*iters)
Das window
Rezept ist das gleiche wie für pairwise
, es ersetzt nur das einzelne Element "verbrauchen" auf dem zweiten tee
Iterator durch progressiv steigende Verzehr auf n - 1
Iteratoren. Die Verwendung, consume
anstatt jeden Iterator einzuschließen, islice
ist geringfügig schneller (für ausreichend große Iterables), da Sie den islice
Umbruchaufwand nur während der consume
Phase zahlen , nicht während des Extrahierens jedes Fensters (also wird er durch n
die Anzahl der Elemente begrenzt) iterable
).
In Bezug auf die Leistung ist dies im Vergleich zu einigen anderen Lösungen ziemlich gut (und besser als alle anderen Lösungen, die ich im Maßstab getestet habe). Getestet unter Python 3.5.0, Linux x86-64, mit ipython
%timeit
Magie.
Kindall ist die deque
Lösung , gezwickt für die Leistung / Korrektheit durch die Verwendung islice
anstelle eines selbstgerollter Generator Expression und Testen der resultierenden Länge , so dass es keine Ergebnisse liefern , wenn die iterable kürzer als das Fenster ist, sowie die Weitergabe der maxlen
von der deque
positions anstelle von nach Schlüsselwort (macht einen überraschenden Unterschied bei kleineren Eingaben):
>>> %timeit -r5 deque(windowkindall(range(10), 3), 0)
100000 loops, best of 5: 1.87 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 3), 0)
10000 loops, best of 5: 72.6 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 30), 0)
1000 loops, best of 5: 71.6 μs per loop
Wie die zuvor angepasste Kindall-Lösung, jedoch mit jeder yield win
Änderung, yield tuple(win)
sodass das Speichern der Ergebnisse vom Generator funktioniert, ohne dass alle gespeicherten Ergebnisse wirklich eine Ansicht des neuesten Ergebnisses sind (alle anderen vernünftigen Lösungen sind in diesem Szenario sicher) und tuple=tuple
zur Funktionsdefinition hinzugefügt werden um die Nutzung von tuple
von B
in LEGB
nach zu verschieben L
:
>>> %timeit -r5 deque(windowkindalltupled(range(10), 3), 0)
100000 loops, best of 5: 3.05 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 3), 0)
10000 loops, best of 5: 207 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 30), 0)
1000 loops, best of 5: 348 μs per loop
consume
-basierte Lösung wie oben gezeigt:
>>> %timeit -r5 deque(windowconsume(range(10), 3), 0)
100000 loops, best of 5: 3.92 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 3), 0)
10000 loops, best of 5: 42.8 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 30), 0)
1000 loops, best of 5: 232 μs per loop
Gleich wie consume
, aber inlining else
Fall consume
, um Funktionsaufruf und n is None
Test zu vermeiden , um die Laufzeit zu reduzieren, insbesondere für kleine Eingaben, bei denen der Einrichtungsaufwand ein bedeutender Teil der Arbeit ist:
>>> %timeit -r5 deque(windowinlineconsume(range(10), 3), 0)
100000 loops, best of 5: 3.57 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 3), 0)
10000 loops, best of 5: 40.9 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 30), 0)
1000 loops, best of 5: 211 μs per loop
(Randnotiz: Eine Variante pairwise
, die tee
mit dem Standardargument 2 wiederholt verschachtelte tee
Objekte erstellt, sodass jeder Iterator nur einmal vorgerückt und nicht immer häufiger unabhängig verwendet wird, ähnlich wie die Antwort von MrDrFenner, ähnelt der von nicht inline consume
und langsamer als consume
bei allen Tests angegeben, daher habe ich diese Ergebnisse der Kürze halber weggelassen.
Wie Sie sehen können, gewinnt meine optimierte Version der kindall-Lösung die meiste Zeit , wenn Sie sich nicht für die Möglichkeit interessieren, dass der Anrufer Ergebnisse speichern muss, außer im Fall "großer iterierbarer kleiner Fenstergröße" (wo Inline consume
gewinnt ); es verschlechtert sich schnell, wenn die iterierbare Größe zunimmt, während es sich mit zunehmender Fenstergröße überhaupt nicht verschlechtert (jede andere Lösung verschlechtert sich langsamer, wenn die iterierbare Größe zunimmt, verschlechtert sich jedoch auch, wenn die Fenstergröße zunimmt). Es kann sogar durch Einwickeln an den Fall "Bedarfstupel" angepasst werden map(tuple, ...)
, der etwas langsamer läuft als das Einfügen des Tupels in die Funktion, aber es ist trivial (dauert 1-5% länger) und ermöglicht es Ihnen, die Flexibilität des schnelleren Laufens beizubehalten wenn Sie es tolerieren können, wiederholt denselben Wert zurückzugeben.
Wenn Sie Sicherheit gegen das Speichern von Retouren benötigen, consume
gewinnt Inline bei allen bis auf die kleinsten Eingabegrößen (wobei Nicht-Inline consume
etwas langsamer ist, aber ähnlich skaliert). Die auf deque
& tupling basierende Lösung gewinnt aufgrund geringerer Einrichtungskosten nur für die kleinsten Eingaben, und der Gewinn ist gering. es verschlechtert sich stark, wenn das iterable länger wird.
Für die Aufzeichnung der angepassten Version der Lösung des Kindall dass yield
s tuple
s ich verwendet wurde:
def windowkindalltupled(iterable, n=2, tuple=tuple):
it = iter(iterable)
win = deque(islice(it, n), n)
if len(win) < n:
return
append = win.append
yield tuple(win)
for e in it:
append(e)
yield tuple(win)
Löschen Sie das Caching tuple
in der Funktionsdefinitionszeile und die Verwendung von tuple
in jedem yield
, um die schnellere, aber weniger sichere Version zu erhalten.
sum()
odermax()
), sollten Sie berücksichtigen, dass es effiziente Algorithmen gibt, mit denen der neue Wert für jedes Fenster in konstanter Zeit (unabhängig von der Fenstergröße) berechnet werden kann . Ich habe einige dieser Algorithmen in einer Python-Bibliothek zusammengefasst: Rolling .