use std::collections::VecDeque; #[derive(Debug, Clone)] pub struct WeightedPoint { pub time: u64, /// value * weight. /// /// when purging expired items, do not subtract `wt_val * wt`, as `wt_val` /// has already been multiplied by `wt`. Instead, simply substract `wt_val` /// from `w_sum`. pub wt_val: f64, pub wt: f64, } #[derive(Clone)] pub struct WeightedAvgWindow { size: u64, items: VecDeque, w_sum: f64, sum_w: f64, //w_mean: f64, } impl WeightedAvgWindow { pub fn new(size: u64) -> Self { Self { size, items: Default::default(), w_sum: 0.0, sum_w: 0.0, } } /// Removes expired items and updates incremental calculations. /// /// Returns `true` if any items were removed. pub fn purge(&mut self, time: u64) -> bool { let mut n_remove = 0; { let items = &self.items; let w_sum = &mut self.w_sum; let sum_w = &mut self.sum_w; let size = self.size; for expired in items.iter().take_while(|x| time - x.time > size) { *w_sum -= expired.wt_val; *sum_w -= expired.wt; n_remove += 1; } } for _ in 0..n_remove { self.items.pop_front(); } // when items is empty, set w_sum, sum_w to 0.0 let zeroer: f64 = ( ! self.items.is_empty()) as u8 as f64; self.w_sum *= zeroer; self.sum_w *= zeroer; n_remove > 0 } /// Add a new item, updating incremental calculations in the process. pub fn push(&mut self, time: u64, val: f64, wt: f64) { let wt_val: f64 = val * wt; self.w_sum += wt_val; self.sum_w += wt; self.items.push_back(WeightedPoint { time, wt_val, wt }); } /// Calculate the weighted mean from current state of incremental /// accumulators. /// /// Note; this value is not cached. pub fn wt_mean(&self) -> f64 { self.w_sum / self.sum_w } /// Checks whether items `is_empty` before trying to calculate. /// Returns None if items is empty. pub fn checked_wt_mean(&self) -> Option { match self.is_empty() { true => None, false => Some(self.w_sum / self.sum_w), } } /// Purge, push and get `checked_wt_mean`, all in one convenient step. pub fn update(&mut self, time: u64, val: f64, wt: f64) -> Option { self.purge(time); self.push(time, val, wt); self.checked_wt_mean() } pub fn len(&self) -> usize { self.items.len() } pub fn is_empty(&self) -> bool { self.items.is_empty() } }