Просто чтобы показать, как вы можете комбинировать 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
с прогрессивным увеличением потребления на итераторах n - 1
.Использование consume
вместо переноса каждого итератора в islice
незначительно быстрее (для достаточно больших итераций), поскольку вы оплачиваете только издержки переноса islice
во время фазы consume
, а не во время извлечения каждого значения, редактируемого в окне(поэтому он ограничен n
, а не количеством элементов в iterable
).
По производительности, по сравнению с некоторыми другими решениями, это довольно хорошо (и лучше, чем любое другое решение, которое япроверено как оно масштабируется).Протестировано на Python 3.5.0, Linux x86-64, с использованием ipython
%timeit
magic.
Решение deque
kindall , настроенное на производительность / корректность с помощью 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
in 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
& tupling выигрывает только для наименьших входных данных из-за меньших затрат на установку и небольшого усиления;он сильно ухудшается по мере увеличения длины итерации.
Для протокола, адаптированная версия решения 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
, чтобы получить более быструю, но менее безопасную версию.