Solo per mostrare come combinare le itertools
ricette , sto estendendo la pairwise
ricetta il più direttamente possibile nella window
ricetta usando la consume
ricetta:
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
ricetta è la stessa di pairwise
, sostituisce semplicemente il singolo elemento "consuma" sul secondo tee
iteratore con consumi progressivamente crescenti sugli n - 1
iteratori. L'utilizzo consume
anziché il wrapping di ogni iteratore islice
è leggermente più veloce (per iterable sufficientemente grandi) poiché si paga solo il islice
sovraccarico di wrapping durante la consume
fase, non durante il processo di estrazione di ogni valore con finestra (quindi è limitato n
, non dal numero di elementi in iterable
).
Per quanto riguarda le prestazioni, rispetto ad alcune altre soluzioni, questo è abbastanza buono (e migliore di qualsiasi altra soluzione che ho testato in scala). Testato su Python 3.5.0, Linux x86-64, usando la ipython
%timeit
magia.
kindall è la deque
soluzione , ottimizzata per le prestazioni / correttezza utilizzando islice
invece di un'espressione del generatore home-roll e testando la lunghezza risultante in modo che non produca risultati quando l'iterabile è più corto della finestra, oltre a passare il maxlen
del deque
posizionalmente anziché per parola chiave (fa una differenza sorprendente per input più piccoli):
>>> %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
Come la precedente soluzione kindall adattata, ma con ciascuna yield win
modifica in yield tuple(win)
modo da memorizzare i risultati dal generatore funziona senza che tutti i risultati memorizzati siano realmente una vista del risultato più recente (tutte le altre soluzioni ragionevoli sono sicure in questo scenario) e si aggiungono tuple=tuple
alla definizione della funzione per spostare l'uso di tuple
da B
in LEGB
a 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
soluzione di base mostrata sopra:
>>> %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
Uguale consume
, ma inlining else
caso consume
di chiamata di funzione evita e n is None
prova per ridurre runtime, particolarmente per piccoli ingressi dove l'overhead configurazione è una parte significativa del lavoro:
>>> %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 a margine: una variante pairwise
che utilizza tee
ripetutamente l'argomento 2 per creare tee
oggetti nidificati , quindi ogni dato iteratore viene avanzato solo una volta, non consumato in modo indipendente un numero crescente di volte, simile alla risposta di MrDrFenner è simile a non in linea consume
e più lento di quello integrato consume
in tutti i test, quindi ho omesso quei risultati per brevità).
Come puoi vedere, se non ti interessa la possibilità che il chiamante abbia bisogno di memorizzare i risultati, la mia versione ottimizzata della soluzione di kindall vince la maggior parte del tempo, tranne nel "caso iterabile grande, di dimensioni ridotte" (dove consume
vince la linea ); si degrada rapidamente all'aumentare della dimensione iterabile, mentre non diminuisce affatto all'aumentare della dimensione della finestra (ogni altra soluzione si degrada più lentamente all'aumentare della dimensione iterabile, ma diminuisce anche all'aumentare della dimensione della finestra). Può anche essere adattato al caso "bisogno di tuple" avvolgendolo map(tuple, ...)
, che funziona in modo leggermente più lento rispetto al mettere la tupling nella funzione, ma è banale (richiede 1-5% in più) e ti consente di mantenere la flessibilità di correre più veloce quando puoi tollerare ripetutamente la restituzione dello stesso valore.
Se hai bisogno di sicurezza contro la memorizzazione dei ritorni, inline consume
vince su tutti tranne le dimensioni di input più piccole (con non inline consume
leggermente più lento ma ridimensionamento in modo simile). La deque
soluzione basata su & tupling vince solo per gli input più piccoli, a causa di minori costi di installazione e il guadagno è ridotto; si degrada male quando l'iterabile si allunga.
Per la cronaca, la versione adattata della soluzione di Kindall che yield
s tuple
s ho usato è stato:
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)
Eliminare la memorizzazione nella cache tuple
nella riga di definizione della funzione e l'uso di tuple
in ciascuna yield
per ottenere la versione più veloce ma meno sicura.
sum()
Omax()
), vale la pena ricordare che esistono algoritmi efficienti per calcolare il nuovo valore per ogni finestra in tempo costante (indipendentemente dalle dimensioni della finestra). Ho raccolto alcuni di questi algoritmi insieme in una libreria Python: rolling .