use std::collections::VecDeque;
pub const MAX_LOOKBACK: usize = 5000;
#[derive(Debug, Clone)]
pub struct SeriesBuffer<T> {
values: VecDeque<T>,
}
impl<T> Default for SeriesBuffer<T> {
fn default() -> Self {
Self {
values: VecDeque::new(),
}
}
}
impl<T> SeriesBuffer<T> {
pub fn push(&mut self, value: T, capacity: usize) {
self.values.push_front(value);
let capacity = capacity.clamp(1, MAX_LOOKBACK);
while self.values.len() > capacity {
self.values.pop_back();
}
}
pub fn get(&self, offset: usize) -> Option<&T> {
self.values.get(offset)
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.values.iter()
}
}
impl SeriesBuffer<f64> {
pub fn window(&self, n: usize) -> Vec<f64> {
self.values.iter().take(n).copied().collect()
}
pub fn observe(&mut self, value: f64, length: usize) -> Option<Vec<f64>> {
self.push(value, length);
(self.len() >= length).then(|| self.window(length))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keeps_newest_values_up_to_capacity() {
let mut buf = SeriesBuffer::default();
for value in [1.0, 2.0, 3.0, 4.0] {
buf.push(value, 3);
}
assert_eq!(buf.len(), 3);
assert_eq!(buf.window(3), vec![4.0, 3.0, 2.0]);
assert_eq!(buf.get(0), Some(&4.0));
assert_eq!(buf.get(2), Some(&2.0));
assert_eq!(buf.get(3), None);
}
#[test]
fn window_is_short_while_warming_up() {
let mut buf = SeriesBuffer::default();
buf.push(1.0, 5);
buf.push(2.0, 5);
assert_eq!(buf.window(5), vec![2.0, 1.0]);
}
#[test]
fn observe_withholds_a_window_until_it_fills() {
let mut buf = SeriesBuffer::default();
assert_eq!(buf.observe(1.0, 3), None);
assert_eq!(buf.observe(2.0, 3), None);
assert_eq!(buf.observe(3.0, 3), Some(vec![3.0, 2.0, 1.0]));
assert_eq!(buf.observe(4.0, 3), Some(vec![4.0, 3.0, 2.0]));
}
#[test]
fn capacity_is_bounded_by_max_lookback() {
let mut buf = SeriesBuffer::default();
for value in 0..(MAX_LOOKBACK + 10) {
buf.push(value as f64, usize::MAX);
}
assert_eq!(buf.len(), MAX_LOOKBACK);
}
}