use std::collections::{HashMap, VecDeque}; use std::hash::Hash; pub struct LruCache { capacity: usize, clock: u64, entries: HashMap>, order: VecDeque<(K, u64)>, } struct Entry { value: V, generation: u64, } impl LruCache where K: Eq + Hash + Clone, { pub fn new(capacity: usize) -> Self { Self { capacity, clock: 0, entries: HashMap::new(), order: VecDeque::new(), } } pub fn len(&self) -> usize { self.entries.len() } pub fn capacity(&self) -> usize { self.capacity } pub fn is_empty(&self) -> bool { self.entries.is_empty() } pub fn contains_key(&self, key: &K) -> bool { self.entries.contains_key(key) } pub fn get(&mut self, key: &K) -> Option<&V> { let generation = self.next_generation(); let entry = self.entries.get_mut(key)?; entry.generation = generation; self.order.push_back((key.clone(), generation)); Some(&entry.value) } pub fn put(&mut self, key: K, value: V) -> Option { if self.capacity == 0 { return None; } let generation = self.next_generation(); // Insert or refresh the key as the most recently used item. let old = match self.entries.get_mut(&key) { Some(entry) => { entry.generation = generation; Some(std::mem::replace(&mut entry.value, value)) } None => { self.entries.insert(key.clone(), Entry { value, generation }); None } }; self.order.push_back((key, generation)); self.evict_until_within_capacity(); self.compact_if_needed(); old } pub fn remove(&mut self, key: &K) -> Option { self.entries.remove(key).map(|entry| entry.value) } pub fn clear(&mut self) { self.entries.clear(); self.order.clear(); } fn next_generation(&mut self) -> u64 { self.clock = self.clock.wrapping_add(1); self.clock } fn evict_until_within_capacity(&mut self) { while self.entries.len() > self.capacity { if let Some((key, generation)) = self.order.pop_front() { let is_current = self .entries .get(&key) .is_some_and(|entry| entry.generation == generation); if is_current { self.entries.remove(&key); } } else { break; } } } fn compact_if_needed(&mut self) { if self.order.len() <= self.entries.len().saturating_mul(4).max(16) { return; } // Drop stale order entries left behind by repeated accesses. self.order.retain(|(key, generation)| { self.entries .get(key) .is_some_and(|entry| entry.generation == *generation) }); } } #[cfg(test)] mod tests { use super::LruCache; #[test] fn evicts_least_recently_used_item() { let mut cache = LruCache::new(2); cache.put("a", 1); cache.put("b", 2); assert_eq!(cache.get(&"a"), Some(&1)); cache.put("c", 3); assert_eq!(cache.get(&"b"), None); assert_eq!(cache.get(&"a"), Some(&1)); assert_eq!(cache.get(&"c"), Some(&3)); } #[test] fn updating_existing_key_keeps_capacity_and_refreshes_recency() { let mut cache = LruCache::new(2); cache.put("a", 1); cache.put("b", 2); assert_eq!(cache.put("a", 10), Some(1)); cache.put("c", 3); assert_eq!(cache.len(), 2); assert_eq!(cache.get(&"a"), Some(&10)); assert_eq!(cache.get(&"b"), None); assert_eq!(cache.get(&"c"), Some(&3)); } #[test] fn zero_capacity_never_stores_values() { let mut cache = LruCache::new(0); cache.put("a", 1); assert!(cache.is_empty()); assert_eq!(cache.get(&"a"), None); } }