我有几百万个数据点,每个数据点都有时间和值。我很想知道所有的滑动窗口(即4000个数据点的块),窗口从高到低的范围超过了恒定的阈值。
例如:,假设长度为3的窗口,以及高 - 低> 1的阈值。 3.然后系列:[10 12 14 13 10 11 16 14 17]将导致[0,2,4,5],因为这些是3周期窗口的高 - 低范围超过阈值的索引。
我的窗口大小为4000,数据集大小为数百万。
天真的方法是计算每个可能的窗口范围,即1-4000,2-4001,3-4002等,并累积那些超过阈值的集合。对于大型数据集,这可能会像你想象的那样永远。
所以,我认为更好的算法如下:
计算第一个窗口的范围(1-4000),并存储窗口范围的高/低的索引。然后,迭代到(2-4001,3-4002)等。如果窗口最右侧的NEW值高于/低于旧的缓存值,则仅更新高/低索引。
现在,假设1-4000窗口的高/低索引分别为333和666。我迭代并继续更新新的高点/低点,因为我在右边看到它们,但是一旦窗口处于334-4333(一旦缓存的高/低超出当前窗口),我重新计算高/低对于当前窗口(334-4333),缓存并继续迭代。
我的问题是:
1。)是否有一个数学公式可以完全不需要算法?我知道窗口期间有加权和指数移动平均值的公式,不需要重新计算窗口。
2。)我的算法是否合理?准确?有没有办法可以大大简化或改进?
非常感谢。
答案 0 :(得分:3)
如果数据长度为n且窗口大小为m,则这是使用排序映射的O(n log m)解决方案。
(defn freqs
"Like frequencies but uses a sorted map"
[coll]
(reduce (fn [counts x]
(assoc counts x (inc (get counts x 0))))
(sorted-map) coll))
(defn rng
"Return max - min value of a sorted-map (log time)"
[smap]
(- (ffirst (rseq smap)) (ffirst smap)))
(defn slide-threshold [v w t]
(loop [q (freqs (subvec v 0 w)), i 0, j (+ i w), a []]
(if (= (count v) j)
a
(let [q* (merge-with + q {(v i) -1} {(v j) 1})
q* (if (zero? (q* (v i))) (dissoc q* (v i)) q*)
a* (if (> (rng q) t) (conj a i) a)]
(recur q* (inc i) (inc j) a*)))))
(slide-threshold [10 12 14 13 10 11 16 14 17] 3 3)
;=> [0 2 4 5]
答案 1 :(得分:2)
天真的版本不是线性的。线性将是O(n)。朴素算法是O(n * k),其中k是窗口大小。在最坏的情况下,你的改进也是O(n * k)(想象一个排序的数组),但在一般情况下你应该看到运行时间有很大的改进,因为你将避免大量的重新计算。
您可以使用Min-max heap(或两个堆)在O(n log k)中解决此问题,但您必须使用一种可以删除O(log k)中的任意节点的堆。您不能使用标准二进制堆,因为虽然删除任意节点是O(log k),但查找节点是O(k)。
假设您有一个Min-max堆,算法如下所示:
heap = create empty heap
add first k items to the heap
for (i = k; i < n-k; ++i)
{
if (heap.MaxItem - heap.MinItem) > threshold
output range
remove item i-k from the heap
add item i to the heap
}
问题当然是从堆中删除项目i-k。实际上,问题是有效地找到它。我过去这样做的方法是修改我的二进制堆,以便它存储包含索引和值的节点。当然,堆比较使用该值。索引是节点在后备阵列中的位置,并且每当移动节点时由堆更新。将项添加到堆中时,Add方法返回对节点的引用,我将其保存在数组中。或者在您的情况下,您可以将其保留在队列中。
所以算法看起来像这样:
queue = create empty queue of heap nodes
heap = create empty heap
for (i = 0; i < k; ++i)
{
node = heap.Add(array[i]);
queue.Add(node);
}
for (i = k; i < n-k; ++i)
{
if (heap.MaxItem - heap.MinItem) > threshold
output range
node = queue.Dequeue()
remove item at position node.Index from the heap
node = heap.Add(array[i])
queue.Add(node)
}
这可证明是O(n log k)。读取每个项目并将其添加到堆中。实际上,它也从堆中删除了。此外,每个项目都会添加到队列中并从队列中删除,但这两个操作都是O(1)。
对于那些怀疑我的人, 可以在O(log k)时间从堆中删除任意元素,前提是你知道它在哪里。我在这里解释了这个技术:https://stackoverflow.com/a/8706363/56778。
因此,如果您有一个大小为4,000的窗口,则运行时间将大致与:3n * 2(log k)
成比例。鉴于一百万件商品和一个窗口大小为5,000,这可以达到3,000,000 *(12.3 * 2),或大约7500万。这大致相当于必须在优化的天真方法中重新计算完整窗口200次。
正如我所说,如果数组已经排序,那么优化后的方法可能会花费很长时间。我上面概述的堆算法不会受此影响。
您应该尝试“更好”的算法,看看它是否足够快。如果它是,并且您不期望病理数据,那么很好。否则请看一下这种技术。
答案 2 :(得分:1)
有一些算法可以在滑动窗口中保持最小(或最大)值,每个元素的摊销复杂度为O(1)(所有数据集为O(N))。这是其中一个使用Deque数据结构,其中包含值/索引对。对于Min和Max,你必须保留两个deques(最大长度为4000)。
at every step:
if (!Deque.Empty) and (Deque.Head.Index <= CurrentIndex - T) then
Deque.ExtractHead;
//Head is too old, it is leaving the window
while (!Deque.Empty) and (Deque.Tail.Value > CurrentValue) do
Deque.ExtractTail;
//remove elements that have no chance to become minimum in the window
Deque.AddTail(CurrentValue, CurrentIndex);
CurrentMin = Deque.Head.Value
//Head value is minimum in the current window
答案 3 :(得分:0)
以下是此代码的python代码:
import heapq
l = [10,12, 14, 13, 10, 11, 16, 14, 17]
w = 3
threshold = 3
breached_indexes = []
#set up the heap for the initial window size
min_values = [(l[i], i) for i in range(0,w)]
max_values = [(-l[i], i) for i in range(0,w)]
heapq.heapify(min_values)
heapq.heapify(max_values)
#check if first window violates the add the index
if (threshold <= -max_values[0][0] - min_values[0][0]):
breached_indexes.append(0)
for i in range(1, len(l)-w+1):
#remove all elements before the current index
while min_values[0][1] < i:
heapq.heappop(min_values)
while max_values[0][1] < i:
heapq.heappop(max_values)
#check the breach
if (threshold <= -max_values[0][0] - min_values[0][0]):
breached_indexes.append(i)
if (i+w >= len(l)):
break
#push the next element entering the window
heapq.heappush(min_values, (l[i+w], i+w))
heapq.heappush(max_values, (-l[i+w], i+w))
print breached_indexes
说明:
在移动窗口的每一步,执行以下操作
一个。从堆中删除项目,直到项目的索引不会下降 在窗外面 湾检查是否违反了阈值 如果需要,堆的顶部元素并记录索引 C。将新进入窗口的元素推入堆中。
*我对max_heap使用负值,因为python的实现是最小堆
此算法的最坏情况复杂性为O(n log n).
答案 4 :(得分:-1)
只想玩一个受Simple Moving Average概念启发的想法。
让我们考虑使用大小为4的滑动窗口的9个点。在任何时候,我们都会跟踪分别为4,3,2和1的所有窗口的最大值。在那时候。假设我们将它们存储在数组中......
粗略地说,它看起来像这样:
1 2 3 4 5 6 7 8 9
1 1 1 1
x 2 2 2 2
x x 3 3 3 3
x x x 4 4 4 4
5 5 5 5
6 6 6 6
7 7 7
8 8
9
这可以概括如下:
Let
n number of datapoints
s window size, 1 <= s <= n
i current position / datapoint, 1 <= s <= n
Vi value at position i
Ai array at position i (note: the array starts at 1 in this definition)
then
Ai (i <= s) has elements
aj = max(Vi, Ai-1[j]) for j in (1..i-1)
aj = Vi for j = i
aj = undefined/unimportant for j in (i+1..s)
Ai (i > s) has elements
aj = max(Vi, Ai-1[j+1]) for j in (1..s-1)
aj = Vi for j = s
位置i的大小为s的窗口的最大值由Ai [1]给出。此外,可以获得任意大小的窗口x(0&lt)的最大值奖励由Ai [s - x + 1]给出的; x&lt; = s)。
我认为以下情况属实:
我在github上为Javascript添加了一个示例实现/测试平台 - SlidingWindowAlgorithm。这是算法本身的副本(请注意,在此实现中,数组的索引为0):
var evalMaxInSlidingWindow = function(datapoints, windowsize){
var Aprev = [];
var Acurr = [];
var Aresult = [];
for (var i = 0, len = datapoints.length; i < len; i++)
{
if (i < windowsize)
{
for(var j = 0; j < windowsize; j++)
{
if (j < i)
{
Acurr[j] = Math.max(datapoints[i], Aprev[j]);
}
if (j == i)
{
Acurr[j] = datapoints[i];
}
}
}
else
{
for(var j = 0; j < windowsize; j++)
{
if (j < windowsize - 1)
{
Acurr[j] = Math.max(datapoints[i], Aprev[j + 1]);
}
if (j == windowsize - 1)
{
Acurr[j] = datapoints[i];
}
}
}
Aresult.push(Acurr[0]);
Aprev = [].concat(Acurr);
}
return Aresult;
};
在与Scott讨论后,似乎这个算法没有什么特别之处。好吧,玩它很有趣。 :)