use std::collections::HashMap; use std::hash::Hash; struct Node { key: K, value: V, prev: Option, next: Option, } /// A least-recently-used cache with a fixed capacity. /// /// Lookup and insertion are O(1) on average. pub struct LruCache { capacity: usize, entries: HashMap, nodes: Vec>>, free: Vec, most_recent: Option, least_recent: Option, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { capacity, entries: HashMap::with_capacity(capacity), nodes: Vec::with_capacity(capacity), free: Vec::new(), most_recent: None, least_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) } /// Returns a value and marks it as most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { let index = *self.entries.get(key)?; self.touch(index); Some(&self.nodes[index].as_ref().unwrap().value) } /// Returns a mutable value and marks it as most recently used. pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { let index = *self.entries.get(key)?; self.touch(index); Some(&mut self.nodes[index].as_mut().unwrap().value) } /// Inserts a value, evicting the least recently used entry if necessary. /// 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.touch(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, prev: None, next: None, }); self.entries.insert(key, index); self.attach_front(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.most_recent = None; self.least_recent = None; } fn touch(&mut self, index: usize) { if self.most_recent != Some(index) { self.detach(index); self.attach_front(index); } } fn detach(&mut self, index: usize) { let (prev, next) = { let node = self.nodes[index].as_ref().unwrap(); (node.prev, node.next) }; match prev { Some(i) => self.nodes[i].as_mut().unwrap().next = next, None => self.most_recent = next, } match next { Some(i) => self.nodes[i].as_mut().unwrap().prev = prev, None => self.least_recent = prev, } let node = self.nodes[index].as_mut().unwrap(); node.prev = None; node.next = None; } fn attach_front(&mut self, index: usize) { let old_front = self.most_recent; { let node = self.nodes[index].as_mut().unwrap(); node.prev = None; node.next = old_front; } if let Some(front) = old_front { self.nodes[front].as_mut().unwrap().prev = Some(index); } else { self.least_recent = Some(index); } self.most_recent = Some(index); } 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); } } } fn main() {} #[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_an_entry_refreshes_it() { let mut cache = LruCache::new(2); cache.put(1, "old"); cache.put(2, "two"); assert_eq!(cache.put(1, "new"), Some("old")); cache.put(3, "three"); assert_eq!(cache.get(&1), Some(&"new")); assert_eq!(cache.get(&2), None); assert_eq!(cache.remove(&3), Some("three")); } }