use std::collections::{HashMap, VecDeque}; use std::hash::Hash; /// A simple least-recently-used cache. /// /// Lookups and inserts are O(n) because refreshing recency removes a key from /// a VecDeque. This keeps the implementation compact and self-contained. #[derive(Debug, Clone)] pub struct LruCache { capacity: usize, map: HashMap, order: VecDeque, // Least recent at the front, most recent at the back. } impl LruCache where K: Eq + Hash + Clone, { pub fn new(capacity: usize) -> Self { Self { capacity, map: HashMap::new(), order: VecDeque::new(), } } pub fn capacity(&self) -> usize { self.capacity } pub fn len(&self) -> usize { self.map.len() } pub fn is_empty(&self) -> bool { self.map.is_empty() } pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) } /// Returns a value and marks it as most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { if !self.map.contains_key(key) { return None; } self.touch(key); self.map.get(key) } /// Returns a mutable value and marks it as most recently used. pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { if !self.map.contains_key(key) { return None; } self.touch(key); self.map.get_mut(key) } /// Inserts or updates a value. If capacity is exceeded, evicts the LRU item. pub fn put(&mut self, key: K, value: V) -> Option<(K, V)> { if self.capacity == 0 { return Some((key, value)); } if self.map.contains_key(&key) { self.map.insert(key.clone(), value); self.touch(&key); return None; } self.map.insert(key.clone(), value); self.order.push_back(key); if self.map.len() > self.capacity { self.evict_lru() } else { None } } pub fn remove(&mut self, key: &K) -> Option { self.remove_from_order(key); self.map.remove(key) } pub fn clear(&mut self) { self.map.clear(); self.order.clear(); } fn touch(&mut self, key: &K) { self.remove_from_order(key); self.order.push_back(key.clone()); } fn remove_from_order(&mut self, key: &K) { if let Some(index) = self.order.iter().position(|existing| existing == key) { self.order.remove(index); } } fn evict_lru(&mut self) -> Option<(K, V)> { while let Some(key) = self.order.pop_front() { if let Some(value) = self.map.remove(&key) { return Some((key, value)); } } None } } fn main() {} #[cfg(test)] mod tests { use super::LruCache; #[test] fn evicts_least_recently_used_entry() { let mut cache = LruCache::new(2); cache.put("a", 1); cache.put("b", 2); assert_eq!(cache.get(&"a"), Some(&1)); let evicted = cache.put("c", 3); assert_eq!(evicted, Some(("b", 2))); 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(1, "one"); cache.put(2, "two"); cache.put(1, "ONE"); let evicted = cache.put(3, "three"); assert_eq!(evicted, Some((2, "two"))); assert_eq!(cache.len(), 2); assert_eq!(cache.get(&1), Some(&"ONE")); assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&3), Some(&"three")); } }