Juste pour montrer comment vous pouvez combiner des itertoolsrecettes , je prolonge la pairwiserecette aussi directement que possible dans la windowrecette en utilisant la consumerecette:
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 windowrecette est la même que pour pairwise, elle remplace simplement l'élément unique «consommer» sur le deuxième teeitérateur avec des consommations progressivement croissantes sur les n - 1itérateurs. Utiliser consumeau lieu d'encapsuler chaque itérateur dans isliceest légèrement plus rapide (pour des itérables suffisamment volumineux) puisque vous ne payez les islicefrais généraux d'encapsulation que pendant la consumephase, 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 dequesolution , ajustée pour la performance / l'exactitude en utilisant isliceau 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 maxlende la dequeposition 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 winmodifications 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 tuplede l' Bentrée LEGBvers 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
consumesolution à 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 elsecas de consumepour éviter l'appel de fonction et de n is Nonetester 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 pairwisequi utilise teel'argument par défaut de 2 à plusieurs reprises pour créer des teeobjets 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 consumeet plus lent que l'inline consumesur 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 consumegagne ); 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' consumeemportent 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 yields tuplede 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 tupleen cache de dans la ligne de définition de fonction et l'utilisation de tupledans chacune yieldpour 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 .