Aby pokazać, jak można łączyć itertoolsprzepisy , rozszerzam pairwiseprzepis tak bezpośrednio, jak to możliwe, z powrotem do windowprzepisu, korzystając z consumeprzepisu:
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)
windowRecepta 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 - 1iteratory. Używanie consumezamiast zawijania każdego iteratora islicejest marginalnie szybsze (dla wystarczająco dużych iterable), ponieważ płacisz za islicezawijanie tylko podczas consumefazy, 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ę dequerozwiązanie , grunt pod wydajności / brytyjski stosując islicezamiast generatora ekspresji domowego walcowane i testowanie powstałych długość tak, że nie daje wyniki, gdy iterowalny jest krótszy niż okno, jak przepuszczanie maxlenz dequepozycyjnie 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 winzmianie 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=tupledo definicji funkcji przenieść wykorzystania tuplez Bsię LEGBdo 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
consumerozwią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 elseprzypadkiem, consumeaby uniknąć wywoływania funkcji i n is Nonetestowania 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 pairwisetego używa teez domyślnym argumentem 2 wielokrotnie do tworzenia teeobiektó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 consumei 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 consumewygrywa ); 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 consumewygrywa dla wszystkich oprócz najmniejszych rozmiarów wejściowych (gdzie nie-inline consumejest 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 yieldS tupleS 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 tuplew linii definicji funkcji i użyj tuplew każdym yieldz 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 .