use std::collections::HashMap; use std::hash::Hash; use std::rc::Rc; struct Node { key: Rc, value: V, newer: Option, older: Option, } /// An O(1) least-recently-used cache. /// /// Reading or updating an entry marks it as most recently used. Inserting beyond /// the configured capacity evicts the least recently used entry. pub struct LruCache { capacity: usize, entries: HashMap, usize>, nodes: Vec>>, free: Vec, newest: Option, oldest: Option, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { capacity, entries: HashMap::with_capacity(capacity), nodes: Vec::with_capacity(capacity), free: Vec::new(), newest: None, oldest: 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) } /// Returns a value and promotes its entry to most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { let index = *self.entries.get(key)?; self.promote(index); self.nodes[index].as_ref().map(|node| &node.value) } /// Returns a mutable value and promotes its entry to most recently used. pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { let index = *self.entries.get(key)?; self.promote(index); self.nodes[index].as_mut().map(|node| &mut node.value) } /// Inserts a value. Returns the previous value when the key already existed. pub fn put(&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.promote(index); return Some(old); } if self.capacity == 0 { return None; } let key = Rc::new(key); let index = self.allocate(Node { key: Rc::clone(&key), value, newer: None, older: self.newest, }); if let Some(previous_newest) = self.newest { self.nodes[previous_newest].as_mut().unwrap().newer = Some(index); } else { self.oldest = Some(index); } self.newest = Some(index); self.entries.insert(key, index); if self.entries.len() > self.capacity { self.pop_lru(); } None } pub fn remove(&mut self, key: &K) -> Option<(K, V)> { let index = *self.entries.get(key)?; Some(self.remove_index(index)) } /// Removes and returns the least recently used entry. pub fn pop_lru(&mut self) -> Option<(K, V)> { self.oldest.map(|index| self.remove_index(index)) } pub fn clear(&mut self) { self.entries.clear(); self.nodes.clear(); self.free.clear(); self.newest = None; self.oldest = None; } fn allocate(&mut self, node: Node) -> usize { if let Some(index) = self.free.pop() { self.nodes[index] = Some(node); index } else { self.nodes.push(Some(node)); self.nodes.len() - 1 } } fn promote(&mut self, index: usize) { if self.newest == Some(index) { return; } let (newer, older) = { let node = self.nodes[index].as_ref().unwrap(); (node.newer, node.older) }; if let Some(newer_index) = newer { self.nodes[newer_index].as_mut().unwrap().older = older; } if let Some(older_index) = older { self.nodes[older_index].as_mut().unwrap().newer = newer; } else { self.oldest = newer; } if let Some(current_newest) = self.newest { self.nodes[current_newest].as_mut().unwrap().newer = Some(index); } let node = self.nodes[index].as_mut().unwrap(); node.newer = None; node.older = self.newest; self.newest = Some(index); } fn remove_index(&mut self, index: usize) -> (K, V) { let node = self.nodes[index].take().unwrap(); if let Some(newer) = node.newer { self.nodes[newer].as_mut().unwrap().older = node.older; } else { self.newest = node.older; } if let Some(older) = node.older { self.nodes[older].as_mut().unwrap().newer = node.newer; } else { self.oldest = node.newer; } self.entries.remove(node.key.as_ref()); self.free.push(index); let key = match Rc::try_unwrap(node.key) { Ok(key) => key, Err(_) => unreachable!("cache retained an unexpected key reference"), }; (key, node.value) } } fn main() {} #[cfg(test)] mod tests { use super::LruCache; #[test] fn evicts_the_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_preserves_capacity_and_promotes_the_entry() { let mut cache = LruCache::new(2); cache.put("a", 1); cache.put("b", 2); assert_eq!(cache.put("a", 10), Some(1)); cache.put("c", 3); assert_eq!(cache.len(), 2); assert_eq!(cache.get(&"a"), Some(&10)); assert_eq!(cache.get(&"b"), None); } #[test] fn zero_capacity_never_stores_entries() { let mut cache = LruCache::new(0); cache.put("a", 1); assert!(cache.is_empty()); assert_eq!(cache.get(&"a"), None); } }