use std::collections::VecDeque;
pub struct LruQueue {
inner: VecDeque<String>,
}
impl LruQueue {
pub fn with_capacity(cap: usize) -> Self {
Self {
inner: VecDeque::with_capacity(cap),
}
}
pub fn touch(&mut self, model_id: &str) {
self.inner.retain(|id| id != model_id);
self.inner.push_back(model_id.to_string());
}
pub fn evict_lru(&mut self) -> Option<String> {
self.inner.pop_front()
}
pub fn remove(&mut self, model_id: &str) {
self.inner.retain(|id| id != model_id);
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &String> {
self.inner.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn touch_appends_new_entry() {
let mut q = LruQueue::with_capacity(4);
q.touch("a");
q.touch("b");
q.touch("c");
assert_eq!(q.len(), 3);
assert_eq!(q.evict_lru().as_deref(), Some("a"));
}
#[test]
fn touch_existing_promotes_to_mru() {
let mut q = LruQueue::with_capacity(4);
q.touch("a");
q.touch("b");
q.touch("c");
q.touch("a");
assert_eq!(q.evict_lru().as_deref(), Some("b"));
assert_eq!(q.evict_lru().as_deref(), Some("c"));
assert_eq!(q.evict_lru().as_deref(), Some("a"));
}
#[test]
fn evict_lru_from_empty_returns_none() {
let mut q = LruQueue::with_capacity(4);
assert!(q.evict_lru().is_none());
}
#[test]
fn remove_explicit_entry() {
let mut q = LruQueue::with_capacity(4);
q.touch("a");
q.touch("b");
q.remove("a");
assert_eq!(q.len(), 1);
assert_eq!(q.evict_lru().as_deref(), Some("b"));
}
#[test]
fn is_empty_reflects_state() {
let mut q = LruQueue::with_capacity(4);
assert!(q.is_empty());
q.touch("x");
assert!(!q.is_empty());
let _ = q.evict_lru();
assert!(q.is_empty());
}
}