Solo para mostrar cómo puede combinar itertools
recetas , estoy extendiendo la pairwise
receta lo más directamente posible a la window
receta usando la consume
receta:
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 window
receta es la misma que para pairwise
, simplemente reemplaza el elemento individual "consumo" en el tee
iterador de segunda capa con consumos progresivamente crecientes en los n - 1
iteradores. Usar en consume
lugar de ajustar cada iterador islice
es marginalmente más rápido (para iterables lo suficientemente grandes) ya que solo paga la islice
sobrecarga de ajuste durante la consume
fase, 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 deque
solución , ajustada para el rendimiento / corrección mediante el uso islice
de 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 maxlen
de la deque
posició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 win
cambio 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=tuple
a la definición de la función para mover el uso de tuple
de la B
en LEGB
la 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
basada 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 else
caso de consume
evitar llamadas a funciones y n is None
pruebas 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 pairwise
que se usa tee
con el argumento predeterminado de 2 repetidamente para hacer tee
objetos 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 consume
y 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 consume
gana ); 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 consume
ganancias en línea se obtienen en todos los tamaños de entrada, excepto en los más pequeños (el no en línea consume
es ligeramente más lento pero se escala de manera similar). La deque
solució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 yield
s tuple
usé 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 tuple
en caché de la línea de definición de funciones y el uso de tuple
en cada una yield
para 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 .