诀窍如下:您可以通过void update(int time, float value) 随机获取更新。但是,您还需要跟踪更新何时脱离时间窗口,因此您设置了一个“警报”,该“警报”在time + N 上调用,它会删除以前的更新曾经在计算中再次考虑过。
如果这种情况实时发生,您可以请求操作系统调用 void drop_off_oldest_update(int time) 方法,以便在 time + N 处调用
如果这是模拟,您无法从操作系统获得帮助,您需要手动进行。在模拟中,您将使用作为参数提供的时间调用方法(与实时不相关)。然而,一个合理的假设是保证调用是这样的,时间参数正在增加。在这种情况下,您需要维护警报时间值的排序列表,并且对于每个update 和read 调用,您检查时间参数是否大于警报列表的头部。虽然您执行与警报相关的处理(丢弃最旧的更新)更大,但移除磁头并再次检查,直到处理给定时间之前的所有警报。然后进行更新调用。
到目前为止,我一直认为你会为实际计算做什么是显而易见的,但我会详细说明以防万一。我假设您有一个方法 float read (int time) 用于读取值。目标是使此调用尽可能高效。所以你不在每次调用read 方法时计算移动平均值。相反,您预先计算上次更新或上次警报时的值,并通过几个浮点运算“调整”该值,以考虑自上次更新以来的时间流逝。 (即除了可能处理堆积的警报列表之外的恒定数量的操作)。
希望这很清楚——这应该是一个非常简单且非常高效的算法。
进一步优化:剩下的问题之一是如果在时间窗口内发生大量更新,那么很长一段时间内既没有读取也没有更新,然后读取或更新随之而来。在这种情况下,上述算法在增量更新每个正在下降的更新的值方面效率低下。这不是必需的,因为我们只关心超出时间窗口的最后一次更新,所以如果有一种方法可以有效地删除所有较旧的更新,那将会有所帮助。
为此,我们可以修改算法,对更新进行二分搜索,以找到时间窗口之前的最新更新。如果需要“删除”的更新相对较少,则可以增量更新每个删除的更新的值。但如果有许多更新需要删除,那么可以在删除旧更新后从头开始重新计算值。
关于增量计算的附录:我应该在句子中澄清我所说的增量计算的意思通过几个浮点运算“调整”这个值以解释时间的流逝自上次更新以来。初始非增量计算:
从
开始
sum = 0;
updates_in_window = /* set of all updates within window */;
prior_update' = /* most recent update prior to window with timestamp tweaked to window beginning */;
relevant_updates = /* union of prior_update' and updates_in_window */,
然后按时间递增的顺序遍历relevant_updates:
for each update EXCEPT last {
sum += update.value * time_to_next_update;
},
最后
moving_average = (sum + last_update * time_since_last_update) / window_length;。
现在,如果恰好有一个更新从窗口中掉下来,但没有新的更新到来,请将sum 调整为:
sum -= prior_update'.value * time_to_next_update + first_update_in_last_window.value * time_from_first_update_to_new_window_beginning;
(注意它是prior_update',它的时间戳被修改为最后一个窗口的开始)。如果恰好有一个更新进入窗口但没有新的更新脱落,将sum调整为:
sum += previously_most_recent_update.value * corresponding_time_to_next_update.
应该很明显,这是一个粗略的草图,但希望它显示了您如何保持平均值,以便在摊销的基础上每次更新操作 O(1)。但请注意上一段中的进一步优化。另请注意旧答案中提到的稳定性问题,这意味着浮点错误可能会在大量此类增量操作中累积,从而与对应用程序很重要的完整计算结果存在差异。