Просто чтобы показать, как вы можете комбинировать itertools
рецепты , я расширяю pairwise
рецепт как можно напрямую обратно в window
рецепт, используя consume
рецепт:
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
Рецепт такой же , как для pairwise
, он просто заменяет один элемент «потребить» на втором tee
-ED итератора с прогрессивно возрастающей потребляет на n - 1
итераторы. Использование consume
вместо переноса каждого итератора islice
происходит немного быстрее (для достаточно больших итераций), поскольку вы платите только islice
издержки переноса во время consume
фазы, а не во время извлечения каждого значения, редактируемого в окне (поэтому оно ограничено n
, а не количеством элементов в iterable
).
С точки зрения производительности, по сравнению с некоторыми другими решениями, это довольно хорошо (и лучше, чем любое другое решение, которое я тестировал по мере масштабирования). Протестировано на Python 3.5.0, Linux x86-64, с использованием ipython
%timeit
магии.
Kindall это на deque
решение , оптимальное для производительности / корректности, используя islice
вместо выражения генератора домашнего проката и тестирования полученной длины , так что не дают результаты , когда итерируемое короче , чем окно, а также прохождениям maxlen
из deque
позиционно вместо по ключевому слову (имеет удивительное значение для небольших входных данных):
>>> %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
То же, что и предыдущее адаптированное решение kindall, но с каждым yield win
изменением на yield tuple(win)
так, что сохранение результатов из генератора работает без того, чтобы все сохраненные результаты действительно являлись просмотром самого последнего результата (все другие разумные решения безопасны в этом сценарии), и добавление tuple=tuple
в определение функции для перемещения использовать tuple
от B
в LEGB
к 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
решение, показанное выше:
>>> %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
То же, что consume
и во встроенном else
случае, consume
чтобы избежать вызова функции и n is None
тестирования для сокращения времени выполнения, особенно для небольших входов, где накладные расходы на установку являются значимой частью работы:
>>> %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
(Примечание: вариант для pairwise
этого, использующий tee
аргумент по умолчанию 2 для создания вложенных tee
объектов несколько раз, поэтому любой данный итератор продвигается только один раз, а не потребляется независимо увеличивающееся количество раз, аналогично ответу MrDrFenner, аналогичному не встроенному consume
и медленнее, чем встроенные consume
во всех тестах, поэтому я опускаю эти результаты для краткости).
Как вы можете видеть, если вы не заботитесь о том, что вызывающей стороне нужно сохранять результаты, моя оптимизированная версия решения kindall выигрывает большую часть времени, за исключением «случая большого итерируемого малого размера окна» (где встроенные consume
выигрыши ); он быстро ухудшается при увеличении итеративного размера, но не уменьшается вообще при увеличении размера окна (любое другое решение ухудшается медленнее при увеличении повторяемого размера, но также ухудшается при увеличении размера окна). Его можно даже адаптировать к случаю map(tuple, ...)
«нужных кортежей», заключив его в оболочку, которая работает немного медленнее, чем добавление кортежей в функцию, но она тривиальна (занимает на 1-5% больше) и позволяет сохранить гибкость работы быстрее когда вы можете терпеть неоднократно возвращая одно и то же значение.
Если вам нужна защита от возврата возвратов, встроенные consume
выигрыши будут у всех, кроме самых маленьких входных размеров (при не встроенном consume
немного медленнее, но с одинаковым масштабированием). deque
& Кортежи выигрывает решение , основанное только для самых маленьких входов, из - за меньшие затраты установки, и коэффициент усиление мало; это ухудшается плохо, поскольку повторяемое становится длиннее.
Для записи, адаптированной версии решения Kindall, что yield
S tuple
S я использовал:
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)
Отбросьте кеширование tuple
в строке определения функции и использование tuple
в каждой, yield
чтобы получить более быструю, но менее безопасную версию.
sum()
илиmax()
), стоит иметь в виду, что существуют эффективные алгоритмы для вычисления нового значения для каждого окна за постоянное время (независимо от размера окна). Я собрал некоторые из этих алгоритмов в библиотеке Python: Rolling .