Aby pokazać, jak można łączyć itertools
przepisy , rozszerzam pairwise
przepis tak bezpośrednio, jak to możliwe, z powrotem do window
przepisu, korzystając z consume
przepisu:
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)
window
Recepta jest taka sama, jak w przypadku pairwise
, to po prostu zastępuje pojedynczy element „konsumować” na drugim tee
-ED iterator ze stopniowo zwiększając zużywa na n - 1
iteratory. Używanie consume
zamiast zawijania każdego iteratora islice
jest marginalnie szybsze (dla wystarczająco dużych iterable), ponieważ płacisz za islice
zawijanie tylko podczas consume
fazy, a nie podczas procesu wyodrębniania każdej wartości w oknie (więc jest ograniczona n
, a nie liczbą elementów w iterable
).
Pod względem wydajności, w porównaniu z niektórymi innymi rozwiązaniami, jest to całkiem niezłe (i lepsze niż jakiekolwiek inne rozwiązanie, które testowałem w miarę skalowania). Testowane w Pythonie 3.5.0, Linux x86-64, przy użyciuipython
%timeit
magii.
kindall znajduje się deque
rozwiązanie , grunt pod wydajności / brytyjski stosując islice
zamiast generatora ekspresji domowego walcowane i testowanie powstałych długość tak, że nie daje wyniki, gdy iterowalny jest krótszy niż okno, jak przepuszczanie maxlen
z deque
pozycyjnie zamiast według słów kluczowych (robi zaskakującą różnicę w przypadku mniejszych danych wejściowych):
>>> %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
Tak samo jak w poprzednim dostosowanym rozwiązaniu kindall, ale po każdej yield win
zmianie na yield tuple(win)
tak zapisywanie wyników z generatora działa bez wszystkich zapisanych wyników, które są w rzeczywistości widokiem najnowszego wyniku (wszystkie inne rozsądne rozwiązania są bezpieczne w tym scenariuszu) i dodają tuple=tuple
do definicji funkcji przenieść wykorzystania tuple
z B
się LEGB
do 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
rozwiązanie bazowe pokazane powyżej:
>>> %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
To samo co consume
, ale z wbudowanym else
przypadkiem, consume
aby uniknąć wywoływania funkcji i n is None
testowania w celu skrócenia czasu działania, szczególnie w przypadku małych danych wejściowych, w których narzut konfiguracji jest znaczącą częścią pracy:
>>> %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
(Na marginesie: wariant pairwise
tego używa tee
z domyślnym argumentem 2 wielokrotnie do tworzenia tee
obiektów zagnieżdżonych , więc każdy podany iterator jest przesuwany tylko raz, a nie niezależnie konsumowany rosnącą liczbę razy, podobnie jak odpowiedź MrDrFennera jest podobna do odpowiedzi niewymienionej consume
i wolniej niż inlineconsume
we wszystkich testach, więc pominąłem te wyniki dla zwięzłości).
Jak widać, jeśli nie przejmujesz się możliwością przechowywania przez dzwoniącego wyników, moja zoptymalizowana wersja rozwiązania kindall wygrywa przez większość czasu, z wyjątkiem „dużego iterowalnego, małego rozmiaru okna” (gdzie inline consume
wygrywa ); degraduje się szybko wraz ze wzrostem iterowalnego rozmiaru, ale w ogóle nie ulega degradacji wraz ze wzrostem rozmiaru okna (każde inne rozwiązanie degraduje się wolniej przy zwiększaniu iterowalnego rozmiaru, ale także degraduje się przy wzroście rozmiaru okna). Można go nawet dostosować do przypadku „potrzeby krotek” przez zawijaniemap(tuple, ...)
, które działa nieco wolniej niż umieszczenie krotki w funkcji, ale jest trywialne (trwa 1-5% dłużej) i pozwala zachować elastyczność działania szybciej kiedy możesz tolerować wielokrotne zwracanie tej samej wartości.
Jeśli potrzebujesz bezpieczeństwa przed przechowywaniem zwrotów, inline consume
wygrywa dla wszystkich oprócz najmniejszych rozmiarów wejściowych (gdzie nie-inline consume
jest nieco wolniejszy, ale skaluje się podobnie). Plikdeque
& Tupling wygrywa rozwiązanie oparte tylko dla najmniejszych nakładów, z powodu mniejszych kosztów instalacyjnych, a zysk jest niewielki; degraduje się źle, gdy iteracja staje się dłuższa.
Dla przypomnienia, dostosowanej wersji rozwiązania kindall że yield
S tuple
S I użyto:
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)
Porzuć buforowanie tuple
w linii definicji funkcji i użyj tuple
w każdym yield
z nich, aby uzyskać szybszą, ale mniej bezpieczną wersję.
sum()
Lubmax()
), warto mieć na uwadze, że istnieją wydajne algorytmy do obliczania nowej wartości dla każdego okna w stałym czasie (niezależnie od rozmiaru okna). Zebrałem niektóre z tych algorytmów razem w bibliotece Pythona: Rolling .