use std::collections::{HashMap, VecDeque}; use std::hash::Hash; /// A small, self-contained least-recently-used cache. /// /// The back of `order` is the most recently used key; the front is evicted first. #[derive(Debug, Clone)] pub struct LruCache { capacity: usize, map: HashMap, order: VecDeque, } impl LruCache where K: Eq + Hash + Clone, { /// Create a cache that stores at most `capacity` 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() } pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) } /// Get a value and mark it as most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { if self.map.contains_key(key) { self.touch(key); self.map.get(key) } else { None } } /// Insert or update a value. /// /// Returns the evicted entry, if capacity was exceeded. 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.order.push_back(key.clone()); self.map.insert(key, value); if self.map.len() > self.capacity { self.evict_lru() } else { None } } pub fn remove(&mut self, key: &K) -> Option { self.order.retain(|existing| existing != key); self.map.remove(key) } pub fn clear(&mut self) { self.map.clear(); self.order.clear(); } fn touch(&mut self, key: &K) { self.order.retain(|existing| existing != key); self.order.push_back(key.clone()); } 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 } } #[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_an_entry_refreshes_its_usage() { let mut cache = LruCache::new(2); cache.put(1, "one"); cache.put(2, "two"); cache.put(1, "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")); } }