#[derive(Debug, Clone)]
pub struct Ring<const N: usize> {
buf: [f64; N],
head: usize,
count: usize,
}
impl<const N: usize> Ring<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buf: [0.0; N],
head: 0,
count: 0,
}
}
#[must_use]
pub const fn is_full(&self) -> bool {
self.count == N
}
#[must_use]
pub const fn len(&self) -> usize {
self.count
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
pub fn push(&mut self, x: f64) -> Option<f64> {
let evicted = if self.count == N {
Some(self.buf[self.head])
} else {
self.count += 1;
None
};
self.buf[self.head] = x;
self.head += 1;
if self.head == N {
self.head = 0;
}
evicted
}
#[must_use]
pub fn chrono_sum(&self) -> f64 {
let mut sum = 0.0;
let mut idx = self.head;
let mut seen = 0;
while seen < self.count {
sum += self.buf[idx];
idx += 1;
if idx == N {
idx = 0;
}
seen += 1;
}
sum
}
}
impl<const N: usize> Default for Ring<N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::Ring;
#[test]
fn fills_then_evicts_oldest() {
let mut ring = Ring::<3>::new();
assert_eq!(ring.push(1.0), None);
assert_eq!(ring.push(2.0), None);
assert!(!ring.is_full());
assert_eq!(ring.push(3.0), None);
assert!(ring.is_full());
assert_eq!(ring.push(4.0), Some(1.0));
assert_eq!(ring.push(5.0), Some(2.0));
}
#[test]
fn chrono_sum_is_oldest_to_newest_after_wraparound() {
let mut ring = Ring::<3>::new();
for x in [1.0, 2.0, 3.0, 4.0, 5.0] {
ring.push(x);
}
assert_eq!(ring.chrono_sum().to_bits(), (3.0_f64 + 4.0 + 5.0).to_bits());
assert_eq!(ring.len(), 3);
}
}