use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct TemporalSmoother<T: Clone + Eq + std::hash::Hash> {
window: VecDeque<T>,
pub window_size: usize,
}
impl<T: Clone + Eq + std::hash::Hash> TemporalSmoother<T> {
#[must_use]
pub fn new(window_size: usize) -> Self {
let effective = window_size.max(1);
Self {
window: VecDeque::with_capacity(effective),
window_size: effective,
}
}
pub fn push(&mut self, class: T) {
if self.window.len() >= self.window_size {
self.window.pop_front();
}
self.window.push_back(class);
}
#[must_use]
pub fn current_class(&self) -> Option<&T> {
if self.window.is_empty() {
return None;
}
let items: Vec<&T> = self.window.iter().collect();
let mut groups: Vec<(&T, usize, usize)> = Vec::new();
for (i, item) in items.iter().enumerate() {
if let Some(g) = groups.iter_mut().find(|(rep, _, _)| *rep == *item) {
g.1 += 1;
g.2 = i; } else {
groups.push((*item, 1, i));
}
}
groups
.into_iter()
.max_by(|a, b| a.1.cmp(&b.1).then_with(|| a.2.cmp(&b.2)))
.map(|(rep, _, _)| rep)
}
#[must_use]
pub fn len(&self) -> usize {
self.window.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.window.is_empty()
}
pub fn clear(&mut self) {
self.window.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_window_returns_none() {
let smoother: TemporalSmoother<u8> = TemporalSmoother::new(4);
assert_eq!(smoother.current_class(), None);
}
#[test]
fn test_single_push_returns_that_class() {
let mut smoother: TemporalSmoother<&str> = TemporalSmoother::new(5);
smoother.push("indoor");
assert_eq!(smoother.current_class(), Some(&"indoor"));
}
#[test]
fn test_mode_is_most_frequent() {
let mut smoother: TemporalSmoother<&str> = TemporalSmoother::new(5);
smoother.push("outdoor");
smoother.push("indoor");
smoother.push("outdoor");
smoother.push("outdoor");
assert_eq!(smoother.current_class(), Some(&"outdoor"));
}
#[test]
fn test_window_eviction() {
let mut smoother: TemporalSmoother<u32> = TemporalSmoother::new(3);
smoother.push(1); smoother.push(2);
smoother.push(2);
smoother.push(3); assert_eq!(smoother.len(), 3);
assert_eq!(smoother.current_class(), Some(&2));
}
#[test]
fn test_clear_resets_window() {
let mut smoother: TemporalSmoother<i32> = TemporalSmoother::new(4);
smoother.push(42);
smoother.push(42);
smoother.clear();
assert!(smoother.is_empty());
assert_eq!(smoother.current_class(), None);
}
#[test]
fn test_window_size_one_immediate_result() {
let mut smoother: TemporalSmoother<bool> = TemporalSmoother::new(1);
smoother.push(true);
assert_eq!(smoother.current_class(), Some(&true));
smoother.push(false);
assert_eq!(smoother.current_class(), Some(&false));
}
#[test]
fn test_len_grows_then_stays_at_capacity() {
let mut smoother: TemporalSmoother<u8> = TemporalSmoother::new(3);
assert_eq!(smoother.len(), 0);
smoother.push(1);
assert_eq!(smoother.len(), 1);
smoother.push(2);
assert_eq!(smoother.len(), 2);
smoother.push(3);
assert_eq!(smoother.len(), 3);
smoother.push(4); assert_eq!(smoother.len(), 3);
}
}