#[derive(Debug, Clone, Copy)]
pub struct FixedHistory<const K: usize> {
data: [u64; K],
len: usize,
cursor: usize,
}
impl<const K: usize> FixedHistory<K> {
pub fn new() -> Self {
Self {
data: [0; K],
len: 0,
cursor: 0,
}
}
pub fn capacity(&self) -> usize {
K
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn record(&mut self, timestamp: u64) {
if K == 0 {
return;
}
self.data[self.cursor] = timestamp;
self.cursor = (self.cursor + 1) % K;
if self.len < K {
self.len += 1;
}
}
pub fn most_recent(&self) -> Option<u64> {
self.kth_most_recent(1)
}
pub fn kth_most_recent(&self, k: usize) -> Option<u64> {
if K == 0 || k == 0 || k > self.len {
return None;
}
let idx = (self.cursor + K - k) % K;
Some(self.data[idx])
}
pub fn to_vec_mru(&self) -> Vec<u64> {
(1..=self.len)
.filter_map(|k| self.kth_most_recent(k))
.collect()
}
pub fn iter(&self) -> Iter<'_, K> {
Iter {
history: self,
pos: 1,
}
}
pub fn clear(&mut self) {
self.len = 0;
self.cursor = 0;
}
pub fn clear_shrink(&mut self) {
self.clear();
}
pub fn approx_bytes(&self) -> usize {
std::mem::size_of::<Self>()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_snapshot_mru(&self) -> Vec<u64> {
self.to_vec_mru()
}
#[cfg(any(test, debug_assertions))]
pub fn debug_validate_invariants(&self) {
assert!(self.len <= K);
if K == 0 {
assert_eq!(self.len, 0);
assert_eq!(self.cursor, 0);
} else {
assert!(self.cursor < K);
}
}
}
impl<const K: usize> Default for FixedHistory<K> {
fn default() -> Self {
Self::new()
}
}
impl<const K: usize> PartialEq for FixedHistory<K> {
fn eq(&self, other: &Self) -> bool {
if self.len != other.len {
return false;
}
for k in 1..=self.len {
if self.kth_most_recent(k) != other.kth_most_recent(k) {
return false;
}
}
true
}
}
impl<const K: usize> Eq for FixedHistory<K> {}
impl<const K: usize> std::hash::Hash for FixedHistory<K> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.len.hash(state);
for k in 1..=self.len {
self.kth_most_recent(k).hash(state);
}
}
}
#[derive(Debug, Clone)]
pub struct Iter<'a, const K: usize> {
history: &'a FixedHistory<K>,
pos: usize, }
impl<'a, const K: usize> Iterator for Iter<'a, K> {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let val = self.history.kth_most_recent(self.pos)?;
self.pos += 1;
Some(val)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.history.len().saturating_sub(self.pos - 1);
(remaining, Some(remaining))
}
}
impl<const K: usize> ExactSizeIterator for Iter<'_, K> {}
#[derive(Debug, Clone)]
pub struct IntoIter<const K: usize> {
history: FixedHistory<K>,
pos: usize,
}
impl<const K: usize> Iterator for IntoIter<K> {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let val = self.history.kth_most_recent(self.pos)?;
self.pos += 1;
Some(val)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.history.len().saturating_sub(self.pos - 1);
(remaining, Some(remaining))
}
}
impl<const K: usize> ExactSizeIterator for IntoIter<K> {}
impl<const K: usize> IntoIterator for FixedHistory<K> {
type Item = u64;
type IntoIter = IntoIter<K>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
history: self,
pos: 1,
}
}
}
impl<'a, const K: usize> IntoIterator for &'a FixedHistory<K> {
type Item = u64;
type IntoIter = Iter<'a, K>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_history_tracks_last_k() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
history.record(30);
assert_eq!(history.to_vec_mru(), vec![30, 20, 10]);
history.record(40);
assert_eq!(history.to_vec_mru(), vec![40, 30, 20]);
assert_eq!(history.kth_most_recent(3), Some(20));
}
#[test]
fn fixed_history_empty_returns_none() {
let history = FixedHistory::<4>::new();
assert!(history.is_empty());
assert_eq!(history.len(), 0);
assert_eq!(history.most_recent(), None);
assert_eq!(history.kth_most_recent(1), None);
assert_eq!(history.to_vec_mru(), Vec::<u64>::new());
}
#[test]
fn fixed_history_kth_bounds() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
assert_eq!(history.kth_most_recent(0), None);
assert_eq!(history.kth_most_recent(3), None);
assert_eq!(history.kth_most_recent(1), Some(20));
assert_eq!(history.kth_most_recent(2), Some(10));
}
#[test]
fn fixed_history_wraps_and_overwrites_oldest() {
let mut history = FixedHistory::<2>::new();
history.record(1);
history.record(2);
assert_eq!(history.to_vec_mru(), vec![2, 1]);
history.record(3);
assert_eq!(history.to_vec_mru(), vec![3, 2]);
assert_eq!(history.most_recent(), Some(3));
assert_eq!(history.kth_most_recent(2), Some(2));
}
#[test]
fn fixed_history_preserves_order_after_multiple_wraps() {
let mut history = FixedHistory::<3>::new();
for t in 1..=6 {
history.record(t);
}
assert_eq!(history.len(), 3);
assert_eq!(history.to_vec_mru(), vec![6, 5, 4]);
assert_eq!(history.kth_most_recent(1), Some(6));
assert_eq!(history.kth_most_recent(2), Some(5));
assert_eq!(history.kth_most_recent(3), Some(4));
}
#[test]
fn fixed_history_debug_invariants_hold() {
let mut history = FixedHistory::<2>::new();
history.record(1);
history.record(2);
history.record(3);
history.debug_validate_invariants();
}
#[test]
fn fixed_history_debug_snapshot_mru() {
let mut history = FixedHistory::<3>::new();
history.record(10);
history.record(20);
history.record(30);
assert_eq!(history.debug_snapshot_mru(), vec![30, 20, 10]);
}
#[test]
fn iter_yields_mru_order() {
let mut h = FixedHistory::<4>::new();
h.record(10);
h.record(20);
h.record(30);
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![30, 20, 10]);
}
#[test]
fn iter_on_empty() {
let h = FixedHistory::<4>::new();
assert_eq!(h.iter().count(), 0);
}
#[test]
fn iter_on_zero_capacity() {
let h = FixedHistory::<0>::new();
assert_eq!(h.iter().count(), 0);
}
#[test]
fn iter_after_wrap() {
let mut h = FixedHistory::<3>::new();
for t in 1..=6 {
h.record(t);
}
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![6, 5, 4]);
}
#[test]
fn iter_partially_filled() {
let mut h = FixedHistory::<5>::new();
h.record(100);
h.record(200);
let v: Vec<_> = h.iter().collect();
assert_eq!(v, vec![200, 100]);
}
#[test]
fn iter_count_matches_len() {
let mut h = FixedHistory::<4>::new();
for t in [10, 20, 30, 40, 50] {
h.record(t);
assert_eq!(h.iter().count(), h.len());
}
}
#[test]
fn iter_exact_size() {
let mut h = FixedHistory::<3>::new();
h.record(1);
h.record(2);
let mut it = h.iter();
assert_eq!(it.len(), 2);
it.next();
assert_eq!(it.len(), 1);
it.next();
assert_eq!(it.len(), 0);
assert!(it.next().is_none());
}
#[test]
fn iter_matches_to_vec_mru() {
let mut h = FixedHistory::<5>::new();
for t in [10, 20, 30, 40, 50, 60] {
h.record(t);
}
let from_iter: Vec<_> = h.iter().collect();
assert_eq!(from_iter, h.to_vec_mru());
}
#[test]
fn ref_into_iter_for_loop() {
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
let mut sum = 0u64;
for t in &h {
sum += t;
}
assert_eq!(sum, 30);
assert_eq!(h.len(), 2); }
#[test]
fn owned_into_iter_for_loop() {
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
h.record(30);
let mut sum = 0u64;
for t in h {
sum += t;
}
assert_eq!(sum, 60);
}
#[test]
fn into_iter_exact_size() {
let mut h = FixedHistory::<4>::new();
h.record(1);
h.record(2);
h.record(3);
let mut it = h.into_iter();
assert_eq!(it.len(), 3);
it.next();
assert_eq!(it.len(), 2);
}
#[test]
fn into_iter_yields_mru_order() {
let mut h = FixedHistory::<3>::new();
h.record(7);
h.record(8);
h.record(9);
let v: Vec<_> = h.into_iter().collect();
assert_eq!(v, vec![9, 8, 7]);
}
#[test]
fn iter_after_clear() {
let mut h = FixedHistory::<3>::new();
h.record(1);
h.record(2);
h.clear();
assert_eq!(h.iter().count(), 0);
assert_eq!(h.into_iter().count(), 0);
}
#[test]
fn eq_same_entries_same_order() {
let mut a = FixedHistory::<3>::new();
let mut b = FixedHistory::<3>::new();
a.record(10);
a.record(20);
a.record(30);
b.record(10);
b.record(20);
b.record(30);
assert_eq!(a, b);
}
#[test]
fn eq_different_cursor_same_logical_content() {
let mut a = FixedHistory::<3>::new();
a.record(1);
a.record(2);
a.record(3);
let mut b = FixedHistory::<3>::new();
b.record(99);
b.record(1);
b.record(2);
b.record(3);
assert_eq!(a, b);
}
#[test]
fn ne_different_len() {
let mut a = FixedHistory::<3>::new();
a.record(10);
let mut b = FixedHistory::<3>::new();
b.record(10);
b.record(20);
assert_ne!(a, b);
}
#[test]
fn ne_different_values() {
let mut a = FixedHistory::<3>::new();
a.record(10);
a.record(20);
let mut b = FixedHistory::<3>::new();
b.record(10);
b.record(99);
assert_ne!(a, b);
}
#[test]
fn eq_empty_histories() {
let a = FixedHistory::<4>::new();
let b = FixedHistory::<4>::new();
assert_eq!(a, b);
}
#[test]
fn eq_after_clear() {
let mut a = FixedHistory::<3>::new();
a.record(1);
a.record(2);
a.record(3);
a.clear();
let b = FixedHistory::<3>::new();
assert_eq!(a, b);
}
#[test]
fn hash_equal_histories_same_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut a = FixedHistory::<3>::new();
a.record(10);
a.record(20);
a.record(30);
let mut b = FixedHistory::<3>::new();
b.record(99);
b.record(10);
b.record(20);
b.record(30);
let hash_of = |h: &FixedHistory<3>| {
let mut s = DefaultHasher::new();
h.hash(&mut s);
s.finish()
};
assert_eq!(hash_of(&a), hash_of(&b));
}
#[test]
fn hash_usable_in_hashmap() {
use std::collections::HashMap;
let mut a = FixedHistory::<2>::new();
a.record(1);
a.record(2);
let mut b = FixedHistory::<2>::new();
b.record(1);
b.record(2);
let mut map = HashMap::new();
map.insert(a, "entry");
assert_eq!(map.get(&b), Some(&"entry"));
}
#[test]
fn copy_produces_independent_value() {
let mut original = FixedHistory::<3>::new();
original.record(10);
original.record(20);
let copy = original;
original.record(99);
assert_eq!(copy.most_recent(), Some(20));
assert_eq!(original.most_recent(), Some(99));
}
#[test]
fn copy_can_be_passed_by_value() {
fn sum_history(h: FixedHistory<3>) -> u64 {
h.iter().sum()
}
let mut h = FixedHistory::<3>::new();
h.record(10);
h.record(20);
h.record(30);
assert_eq!(sum_history(h), 60);
assert_eq!(sum_history(h), 60);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_within_capacity(
timestamps in prop::collection::vec(any::<u64>(), 0..100)
) {
let mut history = FixedHistory::<10>::new();
for ts in timestamps {
history.record(ts);
prop_assert!(history.len() <= history.capacity());
prop_assert!(history.len() <= 10);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_most_recent_is_last_recorded(
timestamps in prop::collection::vec(any::<u64>(), 1..50)
) {
let mut history = FixedHistory::<8>::new();
for &ts in ×tamps {
history.record(ts);
prop_assert_eq!(history.most_recent(), Some(ts));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_vec_mru_length_matches_len(
timestamps in prop::collection::vec(any::<u64>(), 0..50)
) {
let mut history = FixedHistory::<7>::new();
for ts in timestamps {
history.record(ts);
let vec = history.to_vec_mru();
prop_assert_eq!(vec.len(), history.len());
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_kth_matches_vec_mru(
timestamps in prop::collection::vec(any::<u64>(), 1..50)
) {
let mut history = FixedHistory::<6>::new();
for ts in timestamps {
history.record(ts);
}
let vec = history.to_vec_mru();
for k in 1..=history.len() {
prop_assert_eq!(history.kth_most_recent(k), Some(vec[k - 1]));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_invariants_always_hold(
timestamps in prop::collection::vec(any::<u64>(), 0..100)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps {
history.record(ts);
history.debug_validate_invariants();
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_k_zero_returns_none(
timestamps in prop::collection::vec(any::<u64>(), 0..30)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps {
history.record(ts);
prop_assert_eq!(history.kth_most_recent(0), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_k_exceeds_len_returns_none(
timestamps in prop::collection::vec(any::<u64>(), 0..20),
k in 1usize..100
) {
let mut history = FixedHistory::<8>::new();
for ts in timestamps {
history.record(ts);
}
if k > history.len() {
prop_assert_eq!(history.kth_most_recent(k), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_empty_returns_none(k in 0usize..20) {
let history = FixedHistory::<10>::new();
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.most_recent(), None);
prop_assert_eq!(history.kth_most_recent(k), None);
prop_assert_eq!(history.to_vec_mru().len(), 0);
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_wrapping_retains_last_k(
timestamps in prop::collection::vec(1u64..1000, 10..50)
) {
const K: usize = 7;
let mut history = FixedHistory::<K>::new();
for ts in ×tamps {
history.record(*ts);
}
prop_assert_eq!(history.len(), K.min(timestamps.len()));
let expected_len = K.min(timestamps.len());
let expected: Vec<u64> = timestamps[timestamps.len() - expected_len..]
.iter()
.rev()
.copied()
.collect();
prop_assert_eq!(history.to_vec_mru(), expected);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_order_preserved_after_wrapping(
timestamps in prop::collection::vec(any::<u64>(), 5..50)
) {
const K: usize = 5;
let mut history = FixedHistory::<K>::new();
for ts in ×tamps {
history.record(*ts);
}
let vec = history.to_vec_mru();
for (idx, &val) in vec.iter().enumerate() {
prop_assert_eq!(history.kth_most_recent(idx + 1), Some(val));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_length_grows_then_saturates(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
const K: usize = 8;
let mut history = FixedHistory::<K>::new();
for (idx, ts) in timestamps.iter().enumerate() {
history.record(*ts);
let expected_len = (idx + 1).min(K);
prop_assert_eq!(history.len(), expected_len);
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
let mut history = FixedHistory::<6>::new();
for ts in timestamps {
history.record(ts);
}
history.clear();
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.most_recent(), None);
prop_assert_eq!(history.to_vec_mru().len(), 0);
history.debug_validate_invariants();
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_shrink_same_as_clear(
timestamps in prop::collection::vec(any::<u64>(), 1..30)
) {
let mut history1 = FixedHistory::<6>::new();
let mut history2 = FixedHistory::<6>::new();
for ts in ×tamps {
history1.record(*ts);
history2.record(*ts);
}
history1.clear();
history2.clear_shrink();
prop_assert_eq!(history1.len(), history2.len());
prop_assert_eq!(history1.is_empty(), history2.is_empty());
prop_assert_eq!(history1.capacity(), history2.capacity());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
timestamps1 in prop::collection::vec(any::<u64>(), 1..20),
timestamps2 in prop::collection::vec(any::<u64>(), 1..20)
) {
let mut history = FixedHistory::<5>::new();
for ts in timestamps1 {
history.record(ts);
}
history.clear();
for ts in ×tamps2 {
history.record(*ts);
}
prop_assert_eq!(history.len(), timestamps2.len().min(5));
prop_assert_eq!(history.most_recent(), Some(*timestamps2.last().unwrap()));
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_zero_capacity_always_empty(
timestamps in prop::collection::vec(any::<u64>(), 0..30)
) {
let mut history = FixedHistory::<0>::new();
for ts in timestamps {
history.record(ts);
prop_assert!(history.is_empty());
prop_assert_eq!(history.len(), 0);
prop_assert_eq!(history.capacity(), 0);
prop_assert_eq!(history.most_recent(), None);
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_reference_implementation(
timestamps in prop::collection::vec(any::<u64>(), 0..50)
) {
const K: usize = 10;
let mut history = FixedHistory::<K>::new();
let mut reference: Vec<u64> = Vec::new();
for ts in timestamps {
history.record(ts);
reference.push(ts);
if reference.len() > K {
reference.remove(0);
}
prop_assert_eq!(history.len(), reference.len());
if !reference.is_empty() {
prop_assert_eq!(history.most_recent(), Some(*reference.last().unwrap()));
}
let expected: Vec<u64> = reference.iter().rev().copied().collect();
prop_assert_eq!(history.to_vec_mru(), expected);
for k in 1..=reference.len() {
let expected_idx = reference.len() - k;
prop_assert_eq!(history.kth_most_recent(k), Some(reference[expected_idx]));
}
}
}
}
}