Ответ 1
Трюк заключается в следующем: вы получаете обновления в произвольные моменты времени через void update(int time, float value)
. Однако вам также необходимо отслеживать, когда обновление падает с временным окном, поэтому вы устанавливаете "будильник", который вызывается в time + N
, который удаляет предыдущее обновление из когда-либо снова рассмотренных в вычислении.
Если это произойдет в режиме реального времени, вы можете запросить операционную систему для вызова метода void drop_off_oldest_update(int time)
для вызова в time + N
Если это симуляция, вы не можете получить помощь от операционной системы, и вам нужно сделать это вручную. В симуляции вы вызывали бы методы с указанием времени, предоставленного в качестве аргумента (которое не коррелирует с реальным временем). Однако разумное предположение состоит в том, что вызовы гарантированы таким образом, что временные аргументы возрастают. В этом случае вам необходимо сохранить отсортированный список значений времени будильника, и для каждого вызова update
и read
вы проверяете, больше ли аргумент времени, чем глава списка тревог. В то время как это больше, вы выполняете обработку, связанную с аварийным сигналом (оставьте самое старое обновление), удалите головку и снова проверьте, пока не будут обработаны все аварийные сигналы до заданного времени. Затем выполните вызов обновления.
Я до сих пор предполагал, что очевидно, что вы сделали бы для фактического вычисления, но я буду на всякий случай подробно излагать. Я предполагаю, что вы используете метод float read (int time)
, который вы используете для чтения значений. Цель состоит в том, чтобы сделать этот вызов максимально эффективным. Таким образом, вы не вычисляете скользящую среднюю каждый раз, когда вызывается метод read
. Вместо этого вы прекомпретируете значение с последнего обновления или последнего аварийного сигнала и "настроите" это значение на пару операций с плавающей запятой, чтобы учитывать течение времени с момента последнего обновления. (то есть постоянное количество операций, за исключением, возможно, обработки списка сложенных аварийных сигналов).
Надеюсь, это понятно - это должен быть довольно простой алгоритм и достаточно эффективный.
Дальнейшая оптимизация. Одной из оставшихся проблем является то, что во временном окне происходит большое количество обновлений, тогда есть много времени, для которых нет ни чтения, ни обновлений, а затем чтение или обновляется. В этом случае вышеуказанный алгоритм будет неэффективным при постепенном обновлении значения для каждого из падающих обновлений. Это не обязательно, потому что мы заботимся только о последнем обновлении за пределами временного окна, поэтому, если есть способ эффективно отказаться от всех старых обновлений, это поможет.
Чтобы сделать это, мы можем изменить алгоритм, чтобы выполнить двоичный поиск обновлений, чтобы найти последнее обновление перед временным окном. Если имеется относительно небольшое количество обновлений, которые необходимо "отбросить", можно поэтапно обновить значение для каждого сброшенного обновления. Но если есть много обновлений, которые нужно отбросить, то после сброса старых обновлений можно пересчитать значение с нуля.
Приложение для инкрементных вычислений: Я должен уточнить, что я подразумеваю под инкрементным вычислением выше в предложении "tweak" это значение несколькими операциями с плавающей запятой, чтобы учитывать течение времени с момента последнего Обновить. Начальное неинкрементное вычисление:
начните с
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) операции для каждого обновления на амортизированной основе. Но обратите внимание на дальнейшую оптимизацию в предыдущем параграфе. Также обратите внимание на проблемы стабильности, упомянутые в более старом ответе, что означает, что ошибки с плавающей запятой могут накапливаться на большом количестве таких дополнительных операций, так что существует расхождение с результатом полного вычисления, значимого для приложения.