use std::collections::HashMap; use std::hash::Hash; /// A fixed-capacity least-recently-used cache. /// /// Lookups and insertions are expected O(1). Accessing an entry marks it as /// most recently used. pub struct LruCache { capacity: usize, entries: HashMap, nodes: Vec>>, free: Vec, least_recent: Option, most_recent: Option, } struct Node { key: K, value: V, previous: Option, next: Option, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { capacity, entries: HashMap::with_capacity(capacity), nodes: Vec::with_capacity(capacity), free: Vec::new(), least_recent: None, most_recent: None, } } 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 } pub fn contains_key(&self, key: &K) -> bool { self.entries.contains_key(key) } pub fn get(&mut self, key: &K) -> Option<&V> { let index = *self.entries.get(key)?; self.mark_most_recent(index); Some(&self.nodes[index].as_ref().unwrap().value) } pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { let index = *self.entries.get(key)?; self.mark_most_recent(index); Some(&mut self.nodes[index].as_mut().unwrap().value) } /// Inserts a value, returning the previous value for the same key. /// /// If the cache is full, the least recently used entry is evicted. pub fn insert(&mut self, key: K, value: V) -> Option { if let Some(&index) = self.entries.get(&key) { let old = std::mem::replace( &mut self.nodes[index].as_mut().unwrap().value, value, ); self.mark_most_recent(index); return Some(old); } if self.capacity == 0 { return None; } if self.len() == self.capacity { self.evict_least_recent(); } let index = self.free.pop().unwrap_or_else(|| { self.nodes.push(None); self.nodes.len() - 1 }); self.nodes[index] = Some(Node { key: key.clone(), value, previous: self.most_recent, next: None, }); if let Some(previous) = self.most_recent { self.nodes[previous].as_mut().unwrap().next = Some(index); } else { self.least_recent = Some(index); } self.most_recent = Some(index); self.entries.insert(key, index); None } pub fn remove(&mut self, key: &K) -> Option { let index = self.entries.remove(key)?; self.detach(index); let node = self.nodes[index].take().unwrap(); self.free.push(index); Some(node.value) } pub fn clear(&mut self) { self.entries.clear(); self.nodes.clear(); self.free.clear(); self.least_recent = None; self.most_recent = None; } fn mark_most_recent(&mut self, index: usize) { if self.most_recent == Some(index) { return; } self.detach(index); let previous = self.most_recent; { let node = self.nodes[index].as_mut().unwrap(); node.previous = previous; node.next = None; } if let Some(previous) = previous { self.nodes[previous].as_mut().unwrap().next = Some(index); } else { self.least_recent = Some(index); } self.most_recent = Some(index); } fn detach(&mut self, index: usize) { let (previous, next) = { let node = self.nodes[index].as_ref().unwrap(); (node.previous, node.next) }; if let Some(previous) = previous { self.nodes[previous].as_mut().unwrap().next = next; } else { self.least_recent = next; } if let Some(next) = next { self.nodes[next].as_mut().unwrap().previous = previous; } else { self.most_recent = previous; } let node = self.nodes[index].as_mut().unwrap(); node.previous = None; node.next = None; } fn evict_least_recent(&mut self) { if let Some(index) = self.least_recent { self.detach(index); let node = self.nodes[index].take().unwrap(); self.entries.remove(&node.key); self.free.push(index); } } } #[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_eq!(cache.get(&"b"), None); assert_eq!(cache.get(&"a"), Some(&1)); assert_eq!(cache.get(&"c"), Some(&3)); } #[test] fn updates_and_removes_entries() { let mut cache = LruCache::new(2); assert_eq!(cache.insert("a", 1), None); assert_eq!(cache.insert("a", 10), Some(1)); assert_eq!(cache.remove(&"a"), Some(10)); assert!(cache.is_empty()); } }