#[derive(Clone, Copy, Debug)]
pub struct Window<const N: usize> {
samples: [f32; N],
len: usize,
next: usize,
}
impl<const N: usize> Window<N> {
pub fn new() -> Self {
Self {
samples: [0.0; N],
len: 0,
next: 0,
}
}
pub fn push(&mut self, reading: f32) {
if N == 0 {
return;
}
self.samples[self.next] = reading;
self.next = (self.next + 1) % N;
if self.len < N {
self.len += 1;
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn is_full(&self) -> bool {
self.len == N
}
pub fn capacity(&self) -> usize {
N
}
pub fn latest(&self) -> Option<f32> {
if self.len == 0 {
return None;
}
Some(self.samples[(self.next + N - 1) % N])
}
pub fn oldest(&self) -> Option<f32> {
if self.len == 0 {
return None;
}
let index = if self.is_full() { self.next } else { 0 };
Some(self.samples[index])
}
pub fn min(&self) -> Option<f32> {
self.samples[..self.len].iter().copied().reduce(f32::min)
}
pub fn max(&self) -> Option<f32> {
self.samples[..self.len].iter().copied().reduce(f32::max)
}
pub fn range(&self) -> Option<f32> {
Some(self.max()? - self.min()?)
}
pub fn mean(&self) -> Option<f32> {
if self.len == 0 {
return None;
}
let sum: f32 = self.samples[..self.len].iter().sum();
Some(sum / self.len as f32)
}
pub fn variance(&self) -> Option<f32> {
let mean = self.mean()?;
let sum_squared: f32 = self.samples[..self.len]
.iter()
.map(|reading| {
let deviation = reading - mean;
deviation * deviation
})
.sum();
Some(sum_squared / self.len as f32)
}
}
impl<const N: usize> Default for Window<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-4
}
#[test]
fn an_empty_window_has_no_statistics() {
let window = Window::<3>::new();
assert!(window.is_empty());
assert_eq!(window.len(), 0);
assert_eq!(window.min(), None);
assert_eq!(window.max(), None);
assert_eq!(window.mean(), None);
assert_eq!(window.range(), None);
assert_eq!(window.variance(), None);
assert_eq!(window.latest(), None);
assert_eq!(window.oldest(), None);
}
#[test]
fn fills_to_capacity_then_stays_full() {
let mut window = Window::<3>::new();
window.push(1.0);
assert_eq!(window.len(), 1);
assert!(!window.is_full());
window.push(2.0);
window.push(3.0);
assert!(window.is_full());
window.push(4.0);
assert_eq!(window.len(), 3);
assert_eq!(window.capacity(), 3);
}
#[test]
fn reports_spread_over_the_held_readings() {
let mut window = Window::<4>::new();
for reading in [40.0, 42.0, 38.0, 41.0] {
window.push(reading);
}
assert_eq!(window.min(), Some(38.0));
assert_eq!(window.max(), Some(42.0));
assert_eq!(window.range(), Some(4.0));
assert!(approx(window.mean().unwrap(), 40.25));
}
#[test]
fn variance_matches_the_hand_computed_value() {
let mut window = Window::<3>::new();
for reading in [2.0, 4.0, 6.0] {
window.push(reading);
}
assert!(approx(window.variance().unwrap(), 8.0 / 3.0));
}
#[test]
fn the_oldest_reading_is_evicted_when_full() {
let mut window = Window::<3>::new();
for reading in [10.0, 20.0, 30.0, 40.0] {
window.push(reading);
}
assert_eq!(window.oldest(), Some(20.0));
assert_eq!(window.latest(), Some(40.0));
assert_eq!(window.min(), Some(20.0));
assert_eq!(window.max(), Some(40.0));
}
#[test]
fn latest_and_oldest_track_before_the_window_fills() {
let mut window = Window::<5>::new();
window.push(7.0);
assert_eq!(window.latest(), Some(7.0));
assert_eq!(window.oldest(), Some(7.0));
window.push(9.0);
assert_eq!(window.latest(), Some(9.0));
assert_eq!(window.oldest(), Some(7.0));
}
}