use arraydeque::ArrayDeque;
#[derive(Debug, Default)]
pub struct RollingMax<T, const WINDOW: usize> {
deq: ArrayDeque<T, WINDOW>,
ct: usize,
expires: ArrayDeque<usize, WINDOW>,
}
impl<T, const W: usize> RollingMax<T, W>
where
T: PartialOrd,
{
#[must_use]
pub const fn new() -> Self {
Self {
deq: ArrayDeque::new(),
expires: ArrayDeque::new(),
ct: 0,
}
}
#[allow(clippy::expect_used)]
#[allow(clippy::missing_panics_doc)]
pub fn push(&mut self, entry: T) {
self.ct = self.ct.wrapping_add(1);
while self
.expires
.front()
.is_some_and(|&exp| self.ct.wrapping_sub(exp) <= W)
{
self.deq.pop_front();
self.expires.pop_front();
}
while self.deq.back().is_some_and(|tail| tail <= &entry) {
self.deq.pop_back();
self.expires.pop_back();
}
self.deq
.push_back(entry)
.expect("expirations guarantee queue is never full at this point");
self.expires
.push_back(self.ct.wrapping_add(W))
.expect("expirations guarantee queue is never full at this point");
}
#[must_use]
pub fn max(&self) -> Option<&T> {
self.deq.front()
}
}
#[cfg(test)]
pub mod for_tests {
use arraydeque::ArrayDeque;
use arraydeque::Wrapping;
#[derive(Default)]
pub struct NaiveRollingMax<T, const W: usize> {
deq: ArrayDeque<T, W, Wrapping>,
}
impl<T, const W: usize> NaiveRollingMax<T, W>
where
T: Ord,
{
#[must_use]
pub const fn new() -> Self {
Self {
deq: ArrayDeque::new(),
}
}
pub fn push(&mut self, entry: T) {
self.deq.push_back(entry);
}
pub fn max(&self) -> Option<&T> {
self.deq.iter().max()
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::decimal::D4;
use crate::rolling_max::for_tests::NaiveRollingMax;
use core::fmt::Debug;
use rand::distr::Uniform;
use rand::rngs::SmallRng;
use rand::RngExt;
use rand::SeedableRng;
#[test]
fn rng_with_naive() {
const QLEN: usize = 1000;
const STREAM_LEN: usize = 100_000;
let sample =
SmallRng::seed_from_u64(75).sample_iter(Uniform::new(-65.535, 65.535).unwrap());
let mut roller = RollingMax::<D4, QLEN>::new();
let mut naive = NaiveRollingMax::<D4, QLEN>::new();
for val in sample.take(STREAM_LEN) {
let d4 = D4::cast(val);
roller.push(d4);
naive.push(d4);
assert_eq!(roller.max(), naive.max());
}
}
#[test]
fn basic_math() {
let mut rm: RollingMax<i32, 3> = RollingMax::new();
assert_eq!(rm.max(), None);
rm.push(42);
assert_eq!(rm.max(), Some(&42));
}
#[test]
fn basic_rolling() {
expect_max::<i32, 1>([3, 1, 4, 1, 5].into_iter().zip([3, 1, 4, 1, 5]));
expect_max::<i32, 10>([2, 4, 1].into_iter().zip([2, 4, 4]));
expect_max::<i32, 5>([1, 3, 2, 5, 4].into_iter().zip([1, 3, 3, 5, 5]));
expect_max::<i32, 3>([1, 3, 1, 2, 0, 5].into_iter().zip([1, 3, 3, 3, 2, 5]));
expect_max::<i32, 3>([1, 2, 3, 4, 5].into_iter().zip([1, 2, 3, 4, 5]));
expect_max::<i32, 3>([5, 4, 3, 2, 1].into_iter().zip([5, 5, 5, 4, 3]));
expect_max::<i32, 3>([7i32; 6].into_iter().zip([7; 6]));
expect_max::<i32, 2>([-3, -1, -4, -1, -5].into_iter().zip([-3, -1, -1, -1, -1]));
expect_max::<f32, 2>(
[1.0, 3.0, 2.0, 5.0, 4.0]
.into_iter()
.zip([1.0, 3.0, 3.0, 5.0, 5.0]),
);
}
#[test]
fn rollover() {
let mut rm = RollingMax::<i32, 3>::new();
rm.push(99);
rm.push(1);
rm.push(1);
assert_eq!(rm.max(), Some(&99));
rm.push(1);
assert_eq!(rm.max(), Some(&1));
}
#[test]
fn exp_ct_wraps() {
let mut rm: RollingMax<i32, 3> = RollingMax {
deq: ArrayDeque::new(),
expires: ArrayDeque::new(),
ct: usize::MAX - 3,
};
rm.push(10);
rm.push(5);
rm.push(8);
assert_eq!(rm.max(), Some(&10));
rm.push(6);
assert_eq!(rm.max(), Some(&8));
rm.push(7);
assert_eq!(rm.max(), Some(&8));
rm.push(9);
assert_eq!(rm.max(), Some(&9));
}
#[allow(clippy::unwrap_used)]
fn expect_max<T, const WINDOW: usize>(input_and_expected: impl Iterator<Item = (T, T)>)
where
T: PartialOrd + Copy + Debug + PartialEq,
{
let mut rm: RollingMax<T, WINDOW> = RollingMax::new();
for (input, expected) in input_and_expected {
rm.push(input);
assert_eq!(*rm.max().unwrap(), expected);
}
}
}