use std::collections::{HashMap, VecDeque}; use std::hash::Hash; /// A small, self-contained LRU cache. /// /// The front of `order` is the least recently used key; the back is the most /// recently used key. Moving a key is O(n), while lookups are O(1) average. #[derive(Debug, Clone)] pub struct LruCache { capacity: usize, map: HashMap, order: VecDeque, } impl LruCache where K: Eq + Hash + Clone, { /// Create an empty cache with the given maximum number of entries. pub fn new(capacity: usize) -> Self { Self { capacity, map: HashMap::with_capacity(capacity), order: VecDeque::with_capacity(capacity), } } 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() } /// Insert or update a value, returning the old value if the key existed. /// /// The inserted key becomes the most recently used entry. If capacity is /// exceeded, the least recently used entry is evicted. pub fn put(&mut self, key: K, value: V) -> Option { if self.capacity == 0 { return None; } if self.map.contains_key(&key) { self.touch(&key); return self.map.insert(key, value); } if self.map.len() == self.capacity { self.evict_lru(); } self.order.push_back(key.clone()); self.map.insert(key, value) } /// Get a shared reference and mark the key 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) } /// Get a mutable reference and mark the key 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) } /// Remove a key from the cache, returning its value if present. pub fn remove(&mut self, key: &K) -> Option { self.remove_from_order(key); self.map.remove(key) } /// Check for a key without changing its recency. pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) } 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) { if let Some(oldest) = self.order.pop_front() { self.map.remove(&oldest); } } } #[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)); 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_refreshes_recency() { let mut cache = LruCache::new(2); cache.put(1, "one"); cache.put(2, "two"); assert_eq!(cache.put(1, "ONE"), Some("one")); cache.put(3, "three"); assert_eq!(cache.get(&1), Some(&"ONE")); assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&3), Some(&"three")); } #[test] fn zero_capacity_stores_nothing() { let mut cache = LruCache::new(0); cache.put("x", 10); assert_eq!(cache.len(), 0); assert_eq!(cache.get(&"x"), None); } }