Apenas para mostrar como você pode combinar itertools
receitas , estendo a pairwise
receita o mais diretamente possível de volta à window
receita usando a consume
receita:
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)
A window
receita é a mesma que para pairwise
, apenas substitui o elemento único "consumir" no tee
iterador secundário por um aumento progressivo do consumo nos n - 1
iteradores. Usar em consume
vez de agrupar cada iterador islice
é marginalmente mais rápido (para iteráveis suficientemente grandes), pois você paga apenas a islice
sobrecarga do agrupamento durante a consume
fase, não durante o processo de extração de cada valor da janela (portanto, é limitado por n
, não pelo número de itens em iterable
)
Em termos de desempenho, comparado a outras soluções, isso é muito bom (e melhor do que qualquer outra solução que eu testei à medida). Testado em Python 3.5.0, Linux x86-64, usando ipython
%timeit
magia.
kindall é a deque
solução , aprimorada em termos de desempenho / correção usando em islice
vez de uma expressão de gerador rolada em casa e testando o comprimento resultante para que não produza resultados quando o iterável for mais curto que a janela, além de passar o maxlen
da deque
posição em vez de por palavra-chave (faz uma diferença surpreendente para entradas menores):
>>> %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
O mesmo que a solução padrão adaptada anterior, mas com cada yield win
alteração alterada para yield tuple(win)
que o armazenamento de resultados do gerador funcione sem que todos os resultados armazenados sejam realmente uma visão do resultado mais recente (todas as outras soluções razoáveis são seguras nesse cenário) e adicionando tuple=tuple
à definição da função para mover uso de tuple
do B
em LEGB
ao 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
à base de água mostrada acima:
>>> %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
Mesmo que consume
, mas inlining else
caso consume
da chamada de função evitar e n is None
teste para reduzir tempo de execução, especialmente para as pequenas entradas onde a sobrecarga de configuração é uma parte significativa do trabalho:
>>> %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
(Observação: uma variante pairwise
usada tee
com o argumento padrão 2 repetidamente para criar tee
objetos aninhados , portanto, qualquer iterador específico é avançado apenas uma vez, não consumido independentemente um número crescente de vezes, semelhante à resposta de MrDrFenner é semelhante a não alinhado consume
e mais lento que o descrito consume
em todos os testes, por isso omiti esses resultados por questões de brevidade).
Como você pode ver, se você não se importa com a possibilidade de o chamador precisar armazenar resultados, minha versão otimizada da solução da kindall ganha na maioria das vezes, exceto no "caso grande iterável e pequeno do tamanho da janela" (onde o inline consume
vence ); é degradado rapidamente à medida que o tamanho iterável aumenta, sem degradar à medida que o tamanho da janela aumenta (todas as outras soluções degradam mais lentamente para o aumento do tamanho iterável, mas também degradam para o aumento do tamanho da janela). Pode até ser adaptado para o caso "precisar de tuplas", envolvendo map(tuple, ...)
, que é um pouco mais lento do que colocar a tupla na função, mas é trivial (leva 1-5% a mais) e permite manter a flexibilidade de executar mais rapidamente quando você pode tolerar retornar repetidamente o mesmo valor.
Se você precisar de segurança contra o armazenamento de retornos, o inline consume
vence em todos os tamanhos de entrada, exceto os menores (com o não inline consume
sendo um pouco mais lento, mas com escala semelhante). A deque
solução baseada em & tupling ganha apenas nos menores insumos, devido aos menores custos de instalação, e o ganho é pequeno; ele se degrada muito à medida que o iterável fica mais longo.
Para o registro, a versão adaptada da solução da Kindall que yield
s tuple
s que usei foi:
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)
Abandone o armazenamento tuple
em cache na linha de definição de função e o uso de tuple
em cada um yield
para obter a versão mais rápida, mas menos segura.
sum()
oumax()
), vale lembrar que existem algoritmos eficientes para calcular o novo valor para cada janela em tempo constante (independentemente do tamanho da janela). Eu coletei alguns desses algoritmos juntos em uma biblioteca Python: rolling .