Solo para mostrar cómo puede combinar itertoolsrecetas , estoy extendiendo la pairwisereceta lo más directamente posible a la windowreceta usando la consumereceta:
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)
La windowreceta es la misma que para pairwise, simplemente reemplaza el elemento individual "consumo" en el teeiterador de segunda capa con consumos progresivamente crecientes en los n - 1iteradores. Usar en consumelugar de ajustar cada iterador islicees marginalmente más rápido (para iterables lo suficientemente grandes) ya que solo paga la islicesobrecarga de ajuste durante la consumefase, no durante el proceso de extracción de cada valor editado en ventana (por lo que está limitado n, no por el número de elementos en iterable)
En cuanto al rendimiento, en comparación con algunas otras soluciones, esto es bastante bueno (y mejor que cualquiera de las otras soluciones que probé a medida que se escala). Probado en Python 3.5.0, Linux x86-64, usandoipython %timeit magia.
es la dequesolución , ajustada para el rendimiento / corrección mediante el uso islicede una expresión generadora en casa y probando la longitud resultante para que no produzca resultados cuando el iterable es más corto que la ventana, así como pasar el maxlende la dequeposición en lugar de por palabra clave (hace una diferencia sorprendente para entradas más pequeñas):
>>> %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
Igual que la solución adaptada adaptada anterior, pero con cada yield wincambio para yield tuple(win)que los resultados de almacenamiento del generador funcionen sin que todos los resultados almacenados sean realmente una vista del resultado más reciente (todas las otras soluciones razonables son seguras en este escenario) y se agregan tuple=tuplea la definición de la función para mover el uso de tuplede la Ben LEGBla 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
consumebasada en la solución que se muestra arriba:
>>> %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
Igual que consume, pero en el elsecaso de consumeevitar llamadas a funciones y n is Nonepruebas para reducir el tiempo de ejecución, particularmente para entradas pequeñas donde la sobrecarga de configuración es una parte significativa del trabajo:
>>> %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
(Nota al margen: una variante pairwiseque se usa teecon el argumento predeterminado de 2 repetidamente para hacer teeobjetos anidados , por lo que cualquier iterador dado solo se avanza una vez, no se consume independientemente un número creciente de veces, similar a la respuesta de MrDrFenner es similar a no en línea consumey más lento que el en líneaconsume en todas las pruebas, por lo que he omitido esos resultados por brevedad).
Como puede ver, si no le importa la posibilidad de que la persona que llama necesite almacenar resultados, mi versión optimizada de la solución de kindall gana la mayor parte del tiempo, excepto en el "caso de tamaño de ventana pequeño iterativo grande" (donde en línea consumegana ); se degrada rápidamente a medida que aumenta el tamaño iterable, mientras que no se degrada en absoluto a medida que aumenta el tamaño de la ventana (cualquier otra solución se degrada más lentamente cuando aumenta el tamaño iterable, pero también se degrada cuando aumenta el tamaño de la ventana). Incluso se puede adaptar para el caso de "necesidad de tuplas" envolviéndolo map(tuple, ...), que funciona un poco más lento que poner la tupla en la función, pero es trivial (toma 1-5% más tiempo) y le permite mantener la flexibilidad de correr más rápido cuando puede tolerar que se devuelva repetidamente el mismo valor.
Si necesita seguridad contra el retorno de las devoluciones almacenadas, las consumeganancias en línea se obtienen en todos los tamaños de entrada, excepto en los más pequeños (el no en línea consumees ligeramente más lento pero se escala de manera similar). La dequesolución basada en tupling gana solo para las entradas más pequeñas, debido a los menores costos de configuración, y la ganancia es pequeña; se degrada mucho a medida que el iterable se alarga.
Para el registro, la versión adaptada de la solución de kindall que yields tupleusé fue:
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)
Suelte el almacenamiento tupleen caché de la línea de definición de funciones y el uso de tupleen cada una yieldpara obtener la versión más rápida pero menos segura.
sum()omax()) vale la pena tener en cuenta que existen algoritmos eficientes para calcular el nuevo valor de cada ventana en tiempo constante (independientemente del tamaño de la ventana). He reunido algunos de estos algoritmos en una biblioteca de Python: rolling .