循环滑动窗口迭代

Posted

技术标签:

【中文标题】循环滑动窗口迭代【英文标题】:Cyclical Sliding Window Iteration 【发布时间】:2015-05-23 16:54:41 【问题描述】:

考虑一些给定的序列和窗口长度,比如list

a = [13 * i + 1 for i in range(24)]

(这样

In [61]: a
Out[61]: 
[1,
 14,
 27,
 40,
 ...,
 287,
 300]

)

和窗口长度 3.

我想取这个序列的滑动窗口总和,但是是循环的;即计算长度为24的list

[sum([1, 14, 27]),
 sum([14, 27, 40]),
 ...,
 sum([287, 300, 1]),
 sum([300, 1, 14])]

使用collections.deque 和Stupid Lambda Tricks,我能想到的最好的方法是

d = collections.deque(range(24))
d.rotate(1)
map(lambda _: d.rotate(-1) or sum(a[i] for i in list(d)[: 3]), range(24))

还有什么不那么可怕的吗?

【问题讨论】:

【参考方案1】:

那么简单

a = [13 * i + 1 for i in range(24)]
w = 3

aa = a + a[:w]
print([sum(aa[i:i+w]) for i in range(len(a))])

请注意,如果窗口很大,有更好的方法来计算O(n) 中的滑动窗口总和(即每个元素的时间恒定,与窗口大小无关)。

这个想法是做一个“扫描转换”,从一开始就用所有元素的总和替换每个元素(这需要单遍)。

然后在 O(1) 中计算从 x0 到 x1 的元素总和

sum_table[x1] - sum_table[x0]

在代码中:

sum_table = [0]
for v in a:
    sum_table.append(sum_table[-1] + v)
for v in a[:w]:
    sum_table.append(sum_table[-1] + v)
print([sum_table[i+w] - sum_table[i] for i in range(len(a))])

【讨论】:

这在各方面都比我做的要好。想想你答案的后半部分(经典的加头减尾),这也可以使用 Stupid-Lambda-Trick 放在列表理解中,不是吗? @AmiTavory:我更习惯于构建总和表,因为我经常需要随机访问滑动窗口总和,并且只访问一小部分值。当然,您也可以保留一个运行总和并从中加/减。【参考方案2】:

在每个连续的点上,添加新的 (a[i]) 并减去旧的 (a[i-3])。要回绕,您可以链接范围。

s = [sum(a[:3])]
for i in itertools.chain(range(3,len(a)), range(3)) :
  s.append( s[-1] + a[i] - a[i-3] )

【讨论】:

【参考方案3】:

一种快速的方法(至少对于大窗口尺寸):

sums = []
s = sum(a[:3])
for i, n in enumerate(a, 3-len(a)):
    sums.append(s)
    s += a[i] - n

类似,但使用itertools.accumulate

acc = list(accumulate([0] + a + a))
print([acc[i+3] - acc[i] for i in range(len(a))])

或者像 Shashank 一样,但索引为负数:

sums = [sum(a[:3])]
for i in range(-len(a), -1):
    sums.append(sums[-1] - a[i] + a[i+3])

还有一个简短而基本的,再次使用负索引:

[a[i] + a[i+1] + a[i+2] for i in range(-len(a), 0)]

【讨论】:

【参考方案4】:

如果您不介意使用 itertools,这是一种解决方案:

from itertools import cycle, islice
a_one = islice(cycle(a), 1, None)
a_two = islice(cycle(a), 2, None)
sums = [sum(t) for t in zip(a, a_one, a_two)]

您也可以根据窗口长度为此编写一个抽象:

wlen = 3
a_rotations = (islice(cycle(a), i, None) for i in range(1, wlen))
sums = [sum(t) for t in zip(a, *a_rotations)]

这是另一个在窗口长度方面更具可扩展性的解决方案:

alen = len(a)
wlen = 3
sums = [sum(a[:wlen])]
for i in range(alen - 1):
    sums.append(sums[i] - a[i] + a[i + wlen - alen])

另一个有效地结合了这两个想法的解决方案,并借鉴了 Stefan Pochmann 的解决方案中的变量保存想法:

from itertools import islice, cycle
wlen = 3
rotatediterator = islice(cycle(a), wlen, None)
sums = []
lastsum = sum(a[:wlen])
for addval, subval in zip(rotatediterator, a):
    sums.append(lastsum)
    lastsum += addval - subval

【讨论】:

谢谢。对于这种情况,它确实可以完成这项工作,但我不确定它在窗口长度方面的可扩展性(甚至在编写代码方面,而不是运行时复杂性方面)。我对上面 Kasra 的回答写了同样的评论。 @AmiTavory 我已经更新了一个在窗口长度方面更具可扩展性的解决方案。使用第二种方案,时间复杂度和代码复杂度都不受窗口大小的影响很大。 在最后一个中,您可以改为添加a[i + wlen - alen]。我也使用它,负索引规则:-)【参考方案5】:

这适用于任何使用 islice 和 sum 的 n

from itertools import cycle, islice
def rolling_window(func, l, n):
    for i in xrange(len(l)):
        yield func(islice(cycle(l), i, n+i))


print(list(rolling_window(sum,l,3)))

[42, 81, 120, 159, 198, 237, 276, 315, 354, 393, 432, 471, 510, 549, 588, 627, 666, 705, 744, 783, 822, 861, 588, 315]

或者在python3中使用yield:

from itertools import cycle, islice
def rolling_window(func, l, n):
     yield from (func(islice(cycle(l), i, n+i)) for i in range(len(l)))

或者使用模数:

out, ln, n = [], len(l), 3
sm = sum(l[:n])
for i in range(1, 25):
    out.append(sm)
    sm = sm - l[(i - 1) % ln] + l[(i+n-1) % ln]
print(out)

【讨论】:

【参考方案6】:

您可以将mapzip 功能一起使用:

>>> new=a+a[:2]
>>> new
[1, 14, 27, 40, 53, 66, 79, 92, 105, 118, 131, 144, 157, 170, 183, 196, 209, 222, 235, 248, 261, 274, 287, 300, 1, 14]
>>> map(sum,zip(new,new[1:],new[2:]))
[42, 81, 120, 159, 198, 237, 276, 315, 354, 393, 432, 471, 510, 549, 588, 627, 666, 705, 744, 783, 822, 861, 588, 315]
>>> 

请注意,我们创建了new,将a 的前两个元素连接到a 的末尾,zip(new,new[1:],new[2:]) 将为您提供所需的子集,然后您可以使用map 函数来应用sum 函数就可以了:

>>> zip(new,new[1:],new[2:])
[(1, 14, 27), (14, 27, 40), (27, 40, 53), (40, 53, 66), (53, 66, 79), (66, 79, 92), (79, 92, 105), (92, 105, 118), (105, 118, 131), (118, 131, 144), (131, 144, 157), (144, 157, 170), (157, 170, 183), (170, 183, 196), (183, 196, 209), (196, 209, 222), (209, 222, 235), (222, 235, 248), (235, 248, 261), (248, 261, 274), (261, 274, 287), (274, 287, 300), (287, 300, 1), (300, 1, 14)]

【讨论】:

那行得通。但是,假设列表的长度为 10,000,滑动窗口的长度为 793。写zip(new, new[1: ], new[2: ], ... new[792: ]) 怎么可能? @AmiTavory map(sum,zip(*(new[i:] for i in range(3))))

以上是关于循环滑动窗口迭代的主要内容,如果未能解决你的问题,请参考以下文章

带有滑动窗口元素的矩阵

滚动或滑动窗口迭代器?

使用 pandas 滚动的滑动窗口迭代器

使用没有嵌套 while 循环的滑动窗口删除注释

算法和数据结构解析-5 : 滑动窗口问题

算法和数据结构解析-5 : 滑动窗口问题