pub struct SlidingMax {
window_len: usize,
values: Vec<f32>,
deque: Vec<usize>,
deque_head: usize,
deque_len: usize,
position: usize,
}
impl SlidingMax {
pub fn new(window_len: usize) -> Self {
assert!(window_len > 0, "window_len must be greater than 0");
Self {
window_len,
values: vec![0.0; window_len],
deque: vec![0; window_len],
deque_head: 0,
deque_len: 0,
position: 0,
}
}
pub fn push(&mut self, value: f32) {
let index = self.position;
self.position += 1;
self.values[index % self.window_len] = value;
self.evict_expired(index);
self.evict_smaller_from_back(value);
let tail = (self.deque_head + self.deque_len) % self.window_len;
self.deque[tail] = index;
self.deque_len += 1;
}
pub fn max(&self) -> f32 {
self.values[self.deque[self.deque_head] % self.window_len]
}
pub fn reset(&mut self) {
self.position = 0;
self.deque_head = 0;
self.deque_len = 0;
}
fn evict_expired(&mut self, newest_index: usize) {
while self.deque_len > 0 {
let front_index = self.deque[self.deque_head];
if newest_index - front_index < self.window_len {
break;
}
self.deque_head = (self.deque_head + 1) % self.window_len;
self.deque_len -= 1;
}
}
fn evict_smaller_from_back(&mut self, value: f32) {
while self.deque_len > 0 {
let back_index = self.deque[(self.deque_head + self.deque_len - 1) % self.window_len];
if self.values[back_index % self.window_len] > value {
break;
}
self.deque_len -= 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn push_all(tracker: &mut SlidingMax, values: &[f32]) -> f32 {
for &value in values {
tracker.push(value);
}
tracker.max()
}
#[test]
fn tracks_maximum_inside_the_window() {
let mut tracker = SlidingMax::new(3);
assert!((push_all(&mut tracker, &[1.0, 3.0, 2.0]) - 3.0).abs() < 1e-6);
tracker.push(0.5);
assert!((tracker.max() - 3.0).abs() < 1e-6);
tracker.push(0.1);
assert!((tracker.max() - 2.0).abs() < 1e-6);
tracker.push(0.0);
assert!((tracker.max() - 0.5).abs() < 1e-6);
tracker.push(0.0);
assert!((tracker.max() - 0.1).abs() < 1e-6);
}
#[test]
fn maximum_matches_brute_force_for_random_sequence() {
let mut tracker = SlidingMax::new(5);
let values: Vec<f32> = (0..200)
.map(|index| ((index * 37 % 101) as f32 / 101.0).abs())
.collect();
for index in 0..values.len() {
tracker.push(values[index]);
let window_start = (index + 1).saturating_sub(5);
let expected = values[window_start..=index]
.iter()
.fold(0.0_f32, |acc, &value| acc.max(value));
assert!(
(tracker.max() - expected).abs() < 1e-6,
"index {index}: {} vs {expected}",
tracker.max()
);
}
}
#[test]
fn reset_restarts_the_window() {
let mut tracker = SlidingMax::new(2);
tracker.push(1.0);
tracker.push(0.0);
tracker.reset();
tracker.push(0.25);
assert!((tracker.max() - 0.25).abs() < 1e-6);
}
}