Juste pour montrer comment vous pouvez combiner des itertools
recettes , je prolonge la pairwise
recette aussi directement que possible dans la window
recette en utilisant la consume
recette:
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
recette est la même que pour pairwise
, elle remplace simplement l'élément unique «consommer» sur le deuxième tee
itérateur avec des consommations progressivement croissantes sur les n - 1
itérateurs. Utiliser consume
au lieu d'encapsuler chaque itérateur dans islice
est légèrement plus rapide (pour des itérables suffisamment volumineux) puisque vous ne payez les islice
frais généraux d'encapsulation que pendant la consume
phase, pas pendant le processus d'extraction de chaque valeur fenêtrée (il est donc limité par n
, pas par le nombre d'éléments dans iterable
).
En termes de performances, par rapport à certaines autres solutions, c'est plutôt bon (et meilleur que toutes les autres solutions que j'ai testées à mesure qu'elle évolue). Testé sur Python 3.5.0, Linux x86-64, en utilisantipython
%timeit
magie.
kindall est la deque
solution , ajustée pour la performance / l'exactitude en utilisant islice
au lieu d'une expression de générateur lancée à la maison et en testant la longueur résultante afin qu'elle ne donne pas de résultats lorsque l'itérable est plus court que la fenêtre, ainsi que le passage maxlen
de la deque
position au lieu de par mot-clé (fait une différence surprenante pour les entrées plus petites):
>>> %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
Identique à la solution kindall adaptée précédente, mais avec chacune des yield win
modifications apportées, le yield tuple(win)
stockage des résultats du générateur fonctionne sans que tous les résultats stockés ne soient vraiment une vue du résultat le plus récent (toutes les autres solutions raisonnables sont sûres dans ce scénario), et en ajoutant tuple=tuple
à la définition de la fonction pour déplacer l'utilisation de tuple
de l' B
entrée LEGB
vers 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
solution à base illustrée ci-dessus:
>>> %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
Identique à consume
, mais dans le else
cas de consume
pour éviter l'appel de fonction et de n is None
tester pour réduire le temps d'exécution, en particulier pour les petites entrées où la surcharge de configuration est une partie significative du travail:
>>> %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
(Note latérale: une variante pairwise
qui utilise tee
l'argument par défaut de 2 à plusieurs reprises pour créer des tee
objets imbriqués , de sorte que tout itérateur donné n'est avancé qu'une seule fois, pas consommé indépendamment un nombre croissant de fois, similaire à la réponse de MrDrFenner est similaire à non-inline consume
et plus lent que l'inline consume
sur tous les tests, j'ai donc omis ces résultats par souci de concision).
Comme vous pouvez le voir, si vous ne vous souciez pas de la possibilité que l'appelant ait besoin de stocker les résultats, ma version optimisée de la solution de kindall l'emporte la plupart du temps, sauf dans le cas du "grand itérable, petite taille de fenêtre" (où l'inline consume
gagne ); il se dégrade rapidement à mesure que la taille itérable augmente, sans se dégrader du tout à mesure que la taille de la fenêtre augmente (chaque solution sur deux se dégrade plus lentement lorsque la taille itérable augmente, mais se dégrade également lorsque la taille de la fenêtre augmente). Il peut même être adapté pour le cas "besoin de tuples" en enveloppant map(tuple, ...)
, ce qui est un peu plus lent que de mettre le tupling dans la fonction, mais c'est trivial (prend 1-5% de plus) et vous permet de garder la flexibilité de courir plus vite lorsque vous pouvez tolérer le renvoi répété de la même valeur.
Si vous avez besoin de sécurité contre le stockage des retours, les entrées en ligne l' consume
emportent sur toutes les tailles d'entrée sauf les plus petites (la non-ligne consume
étant légèrement plus lente mais mise à l'échelle de la même manière). ledeque
solution basée sur le & tupling ne gagne que pour les plus petites entrées, en raison de coûts de configuration plus faibles, et le gain est faible; il se dégrade gravement à mesure que l'itérable s'allonge.
Pour mémoire, la version adaptée de la solution de Kindall qui yield
s tuple
de j'étais:
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)
Supprimez la mise tuple
en cache de dans la ligne de définition de fonction et l'utilisation de tuple
dans chacune yield
pour obtenir la version la plus rapide mais la moins sûre.
sum()
oumax()
), il convient de garder à l'esprit qu'il existe des algorithmes efficaces pour calculer la nouvelle valeur pour chaque fenêtre en temps constant (quelle que soit la taille de la fenêtre). J'ai rassemblé certains de ces algorithmes dans une bibliothèque Python: rolling .