use std::collections::HashMap; use std::hash::Hash; /// A simple LRU cache. Eviction is O(n); lookup and insertion are O(1) on average. pub struct LruCache { capacity: usize, clock: u128, entries: HashMap>, } struct Entry { value: V, last_used: u128, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { capacity, clock: 0, entries: HashMap::new(), } } pub fn len(&self) -> usize { self.entries.len() } pub fn is_empty(&self) -> bool { self.entries.is_empty() } pub fn capacity(&self) -> usize { self.capacity } /// Returns a value and marks it as most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { self.clock += 1; let now = self.clock; let entry = self.entries.get_mut(key)?; entry.last_used = now; Some(&entry.value) } /// Inserts or replaces a value, returning the old value when present. pub fn insert(&mut self, key: K, value: V) -> Option { if self.capacity == 0 { return None; } self.clock += 1; let now = self.clock; if let Some(entry) = self.entries.get_mut(&key) { entry.last_used = now; return Some(std::mem::replace(&mut entry.value, value)); } if self.entries.len() == self.capacity { self.evict_lru(); } self.entries.insert( key, Entry { value, last_used: now, }, ); None } pub fn remove(&mut self, key: &K) -> Option { self.entries.remove(key).map(|entry| entry.value) } pub fn contains_key(&self, key: &K) -> bool { self.entries.contains_key(key) } fn evict_lru(&mut self) { if let Some(key) = self .entries .iter() .min_by_key(|(_, entry)| entry.last_used) .map(|(key, _)| key) { self.entries.remove(key); } } } fn main() {} #[cfg(test)] mod tests { use super::LruCache; #[test] fn evicts_least_recently_used_entry() { let mut cache = LruCache::new(2); cache.insert("a", 1); cache.insert("b", 2); assert_eq!(cache.get(&"a"), Some(&1)); cache.insert("c", 3); assert!(!cache.contains_key(&"b")); assert_eq!(cache.get(&"a"), Some(&1)); assert_eq!(cache.get(&"c"), Some(&3)); } #[test] fn replacing_an_entry_does_not_evict_another() { let mut cache = LruCache::new(2); cache.insert(1, "old"); cache.insert(2, "second"); assert_eq!(cache.insert(1, "new"), Some("old")); assert_eq!(cache.len(), 2); cache.insert(3, "third"); assert!(!cache.contains_key(&2)); assert_eq!(cache.get(&1), Some(&"new")); } #[test] fn zero_capacity_never_stores_entries() { let mut cache = LruCache::new(0); cache.insert("key", "value"); assert!(cache.is_empty()); assert_eq!(cache.get(&"key"), None); } }