J'ai besoin d'une fenêtre roulante (ou fenêtre coulissante) itérable sur une séquence/itérateur/générateur. L'itération Python par défaut peut être considérée comme un cas spécial, où la longueur de la fenêtre est 1. J'utilise actuellement le code suivant. Quelqu'un a-t-il une méthode plus pythonique, moins verbeuse ou plus efficace pour le faire?
def rolling_window(seq, window_size):
it = iter(seq)
win = [it.next() for cnt in xrange(window_size)] # First window
yield win
for e in it: # Subsequent windows
win[:-1] = win[1:]
win[-1] = e
yield win
if __name__=="__main__":
for w in rolling_window(xrange(6), 3):
print w
"""Example output:
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
"""
Il y en a un dans une ancienne version de la documentation Python avec itertools
examples :
from itertools import islice
def window(seq, n=2):
"Returns a sliding window (of width n) over data from the iterable"
" s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ... "
it = iter(seq)
result = Tuple(islice(it, n))
if len(result) == n:
yield result
for elem in it:
result = result[1:] + (elem,)
yield result
Celui des docs est un peu plus succinct et utilise itertools
avec plus d’effet, j’imagine.
Cela semble être fait sur mesure pour un collections.deque
puisque vous avez essentiellement un FIFO (ajoutez à une extrémité, retirez de l'autre). Cependant, même si vous utilisez une variable list
, vous ne devriez pas découper deux fois; au lieu de cela, vous devriez probablement simplement pop(0)
de la liste et append()
le nouvel élément.
Voici une implémentation optimisée basée sur deque basée sur votre original:
from collections import deque
def window(seq, n=2):
it = iter(seq)
win = deque((next(it, None) for _ in xrange(n)), maxlen=n)
yield win
append = win.append
for e in it:
append(e)
yield win
Dans mes tests, il bat facilement tout le reste affiché ici la plupart du temps, bien que la version tee
de pillmuncher le bat pour les grands itérables et les petites fenêtres. Sur les plus grandes fenêtres, la deque
avance à nouveau à la vitesse brute.
L'accès aux éléments individuels dans la variable deque
peut être plus rapide ou plus lent qu'avec les listes ou les nuplets. (Les éléments proches du début sont plus rapides ou ceux proches de la fin si vous utilisez un index négatif.) Je mets un sum(w)
dans le corps de ma boucle; cela joue sur la force de la déque (itération d’un élément à l’autre est rapide, cette boucle a donc fonctionné 20% plus vite que la méthode la plus rapide suivante, la méthode pillmuncher). Lorsque je l'ai modifiée pour rechercher individuellement et ajouter des éléments dans une fenêtre de dix, les tables tournaient et la méthode tee
était 20% plus rapide. J'ai pu récupérer un peu de vitesse en utilisant des indices négatifs pour les cinq derniers termes de l'addition, mais tee
était encore un peu plus rapide. Dans l’ensemble, j’estimerais que l’un ou l’autre est très rapide pour la plupart des utilisations et si vous avez besoin d’un peu plus de performances, profilez et choisissez celui qui vous convient le mieux.
J'aime tee()
:
from itertools import tee, izip
def window(iterable, size):
iters = tee(iterable, size)
for i in xrange(1, size):
for each in iters[i:]:
next(each, None)
return izip(*iters)
for each in window(xrange(6), 3):
print list(each)
donne:
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
Voici une généralisation qui ajoute le support des paramètres step
, fillvalue
:
from collections import deque
from itertools import islice
def sliding_window(iterable, size=2, step=1, fillvalue=None):
if size < 0 or step < 1:
raise ValueError
it = iter(iterable)
q = deque(islice(it, size), maxlen=size)
if not q:
return # empty iterable or size == 0
q.extend(fillvalue for _ in range(size - len(q))) # pad to size
while True:
yield iter(q) # iter() to avoid accidental outside modifications
try:
q.append(next(it))
except StopIteration: # Python 3.5 pep 479 support
return
q.extend(next(it, fillvalue) for _ in range(step - 1))
Il produit en morceaux size
éléments à la fois en roulant step
positions par itération, en complétant chaque morceau avec fillvalue
si nécessaire. Exemple pour size=4, step=3, fillvalue='*'
:
[a b c d]e f g h i j k l m n o p q r s t u v w x y z
a b c[d e f g]h i j k l m n o p q r s t u v w x y z
a b c d e f[g h i j]k l m n o p q r s t u v w x y z
a b c d e f g h i[j k l m]n o p q r s t u v w x y z
a b c d e f g h i j k l[m n o p]q r s t u v w x y z
a b c d e f g h i j k l m n o[p q r s]t u v w x y z
a b c d e f g h i j k l m n o p q r[s t u v]w x y z
a b c d e f g h i j k l m n o p q r s t u[v w x y]z
a b c d e f g h i j k l m n o p q r s t u v w x[y z * *]
Pour obtenir un exemple de cas d'utilisation du paramètre step
, voir Traitement efficace d'un fichier .txt volumineux en python .
Juste une contribution rapide.
Comme les documents Python actuels ne comportent pas de "fenêtre" dans les exemples itertool (c'est-à-dire, au bas de http://docs.python.org/library/itertools.html ), voici un extrait basé sur le code pour le groupeur qui est l’un des exemples donnés:
import itertools as it
def window(iterable, size):
shiftedStarts = [it.islice(iterable, s, None) for s in xrange(size)]
return it.izip(*shiftedStarts)
Fondamentalement, nous créons une série d’itérateurs tranchés, chacun avec un point de départ un point plus en avant. Ensuite, nous les compressons ensemble. Notez que cette fonction renvoie un générateur (ce n'est pas directement un générateur lui-même).
Tout comme pour les versions d’ajout d’élément et d’avant-itérateur décrites ci-dessus, les performances (c’est-à-dire la meilleure solution) varient en fonction de la taille de la liste et de la taille de la fenêtre. J'aime celui-ci parce qu'il s'agit d'une doublure (ce pourrait être une doublure, mais je préfère nommer les concepts).
Il s'avère que le code ci-dessus est faux. Cela fonctionne si le paramètre passé à iterable est une séquence mais pas s'il s'agit d'un itérateur. Si c'est un itérateur, le même itérateur est partagé (mais pas séparé) entre les appels d'islice et cela casse mal les choses.
Voici un code fixe:
import itertools as it
def window(iterable, size):
itrs = it.tee(iterable, size)
shiftedStarts = [it.islice(anItr, s, None) for s, anItr in enumerate(itrs)]
return it.izip(*shiftedStarts)
En outre, une version de plus pour les livres. Au lieu de copier un itérateur, puis d'avancer plusieurs fois, cette version effectue des copies par paires de chaque itérateur à mesure que nous avancons la position de départ. Ainsi, l'itérateur t fournit à la fois l'itérateur "complet" avec le point de départ en t et également la base pour créer l'itérateur t + 1:
import itertools as it
def window4(iterable, size):
complete_itr, incomplete_itr = it.tee(iterable, 2)
iters = [complete_itr]
for i in xrange(1, size):
incomplete_itr.next()
complete_itr, incomplete_itr = it.tee(incomplete_itr, 2)
iters.append(complete_itr)
return it.izip(*iters)
J'utilise le code suivant comme une simple fenêtre glissante qui utilise des générateurs pour améliorer considérablement la lisibilité. Sa rapidité a jusqu'ici été suffisante pour une utilisation en analyse de séquence bioinformatique, selon mon expérience.
Je l’inclus ici parce que je n’ai pas encore vu cette méthode utilisée. Encore une fois, je n’ai aucune prétention à propos de ses performances comparées.
def slidingWindow(sequence,winSize,step=1):
"""Returns a generator that will iterate through
the defined chunks of input sequence. Input sequence
must be sliceable."""
# Verify the inputs
if not ((type(winSize) == type(0)) and (type(step) == type(0))):
raise Exception("**ERROR** type(winSize) and type(step) must be int.")
if step > winSize:
raise Exception("**ERROR** step must not be larger than winSize.")
if winSize > len(sequence):
raise Exception("**ERROR** winSize must not be larger than sequence length.")
# Pre-compute number of chunks to emit
numOfChunks = ((len(sequence)-winSize)/step)+1
# Do the work
for i in range(0,numOfChunks*step,step):
yield sequence[i:i+winSize]
une version légèrement modifiée de la fenêtre deque, pour en faire une véritable fenêtre évolutive. Pour qu'il commence à être peuplé avec un seul élément, puis qu'il atteigne la taille maximale de la fenêtre, puis qu'il diminue, Edge le reste à la fin:
from collections import deque
def window(seq, n=2):
it = iter(seq)
win = deque((next(it, None) for _ in xrange(1)), maxlen=n)
yield win
append = win.append
for e in it:
append(e)
yield win
for _ in xrange(len(win)-1):
win.popleft()
yield win
for wnd in window(range(5), n=3):
print(list(wnd))
cela donne
[0]
[0, 1]
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4]
[4]
def GetShiftingWindows(thelist, size):
return [ thelist[x:x+size] for x in range( len(thelist) - size + 1 ) ]
>> a = [1, 2, 3, 4, 5]
>> GetShiftingWindows(a, 3)
[ [1, 2, 3], [2, 3, 4], [3, 4, 5] ]
Il y a une bibliothèque qui fait exactement ce dont vous avez besoin:
import more_itertools
list(more_itertools.windowed([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],n=3, step=3))
Out: [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]
def window(seq, size, step=1):
# initialize iterators
iters = [iter(seq) for i in range(size)]
# stagger iterators (without yielding)
[next(iters[i]) for j in range(size) for i in range(-1, -j-1, -1)]
while(True):
yield [next(i) for i in iters]
# next line does nothing for step = 1 (skips iterations for step > 1)
[next(i) for i in iters for j in range(step-1)]
next(it)
soulève StopIteration
lorsque la séquence est terminée, et pour une bonne raison qui me dépasse, l'instruction de rendement ici l'excepte et la fonction retourne, en ignorant les valeurs restantes qui ne forment pas une fenêtre complète.
Quoi qu'il en soit, il s'agit de la solution la moins linéaire mais dont la seule exigence est que seq
implémente soit __iter__
ou __getitem__
et ne repose pas sur itertools
ou collections
à part la solution de @ dansalmo :)
pourquoi pas
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return Zip(a, b)
Il est documenté dans Python doc . Vous pouvez facilement l’étendre à une fenêtre plus large.
def rolling_window(list, degree):
for i in range(len(list)-degree+1):
yield [list[i+o] for o in range(degree)]
Fait cela pour une fonction moyenne glissante
Rendons-le paresseux!
from itertools import islice, tee
def window(iterable, size):
iterators = tee(iterable, size)
iterators = [islice(iterator, i, None) for i, iterator in enumerate(iterators)]
yield from Zip(*iterators)
list(window(range(5), 3))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]
#Importing the numpy library
import numpy as np
arr = np.arange(6) #Sequence
window_size = 3
np.lib.stride_tricks.as_strided(arr, shape= (len(arr) - window_size +1, window_size),
strides = arr.strides*2)
"""Example output:
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
"" "
Que diriez-vous d'utiliser ce qui suit:
mylist = [1, 2, 3, 4, 5, 6, 7]
def sliding_window(l, window_size=2):
if window_size > len(l):
raise ValueError("Window size must be smaller or equal to the number of elements in the list.")
t = []
for i in xrange(0, window_size):
t.append(l[i:])
return Zip(*t)
print sliding_window(mylist, 3)
Sortie:
[(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7)]
>>> n, m = 6, 3
>>> k = n - m+1
>>> print ('{}\n'*(k)).format(*[range(i, i+m) for i in xrange(k)])
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
Modifié Réponse de DiPaolo pour permettre un remplissage arbitraire et une taille de pas variable
import itertools
def window(seq, n=2,step=1,fill=None,keep=0):
"Returns a sliding window (of width n) over data from the iterable"
" s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ... "
it = iter(seq)
result = Tuple(itertools.islice(it, n))
if len(result) == n:
yield result
while True:
# for elem in it:
elem = Tuple( next(it, fill) for _ in range(step))
result = result[step:] + elem
if elem[-1] is fill:
if keep:
yield result
break
yield result
C'est une vieille question, mais pour ceux qui sont toujours intéressés, il existe une excellente implémentation d'un curseur de fenêtre utilisant des générateurs dans this page (par Adrian Rosebrock).
C'est une implémentation pour OpenCV, mais vous pouvez facilement l'utiliser à d'autres fins. Pour les plus enthousiastes, je collerai le code ici, mais pour mieux le comprendre, je vous recommande de visiter la page d'origine.
def sliding_window(image, stepSize, windowSize):
# slide a window across the image
for y in xrange(0, image.shape[0], stepSize):
for x in xrange(0, image.shape[1], stepSize):
# yield the current window
yield (x, y, image[y:y + windowSize[1], x:x + windowSize[0]])
Astuce: Vous pouvez vérifier le .shape
de la fenêtre lors de l'itération du générateur pour ignorer ceux qui ne répondent pas à vos exigences.
À votre santé