use std::collections::HashMap; use std::hash::Hash; /// An LRU cache with O(1) lookup, insertion, and eviction. pub struct LruCache { capacity: usize, map: HashMap, nodes: Vec>>, free: Vec, head: Option, // Most recently used. tail: Option, // Least recently used. } struct Node { key: K, value: V, prev: Option, next: Option, } impl LruCache { pub fn new(capacity: usize) -> Self { Self { capacity, map: HashMap::with_capacity(capacity), nodes: Vec::with_capacity(capacity), free: Vec::new(), head: None, tail: None, } } pub fn len(&self) -> usize { self.map.len() } pub fn capacity(&self) -> usize { self.capacity } pub fn is_empty(&self) -> bool { self.map.is_empty() } /// Returns a value and marks it as most recently used. pub fn get(&mut self, key: &K) -> Option<&V> { let index = *self.map.get(key)?; self.touch(index); Some(&self.node(index).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.map.get(key)?; self.touch(index); Some(&mut self.node_mut(index).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 put(&mut self, key: K, value: V) -> Option { if let Some(&index) = self.map.get(&key) { let old = std::mem::replace(&mut self.node_mut(index).value, value); self.touch(index); return Some(old); } if self.capacity == 0 { return None; } if self.len() == self.capacity { let index = self.tail.expect("a full cache must have a tail"); self.remove_index(index); } let index = if let Some(index) = self.free.pop() { self.nodes[index] = Some(Node { key: key.clone(), value, prev: None, next: None, }); index } else { self.nodes.push(Some(Node { key: key.clone(), value, prev: None, next: None, })); self.nodes.len() - 1 }; self.map.insert(key, index); self.attach_front(index); None } pub fn remove(&mut self, key: &K) -> Option { let index = *self.map.get(key)?; Some(self.remove_index(index).value) } fn touch(&mut self, index: usize) { if self.head != Some(index) { self.detach(index); self.attach_front(index); } } fn detach(&mut self, index: usize) { let (prev, next) = { let node = self.node(index); (node.prev, node.next) }; match prev { Some(prev) => self.node_mut(prev).next = next, None => self.head = next, } match next { Some(next) => self.node_mut(next).prev = prev, None => self.tail = prev, } let node = self.node_mut(index); node.prev = None; node.next = None; } fn attach_front(&mut self, index: usize) { let old_head = self.head; { let node = self.node_mut(index); node.prev = None; node.next = old_head; } if let Some(head) = old_head { self.node_mut(head).prev = Some(index); } else { self.tail = Some(index); } self.head = Some(index); } fn remove_index(&mut self, index: usize) -> Node { self.detach(index); let node = self.nodes[index] .take() .expect("cache index must reference a node"); self.map.remove(&node.key); self.free.push(index); node } fn node(&self, index: usize) -> &Node { self.nodes[index] .as_ref() .expect("cache index must reference a node") } fn node_mut(&mut self, index: usize) -> &mut Node { self.nodes[index] .as_mut() .expect("cache index must reference a node") } } #[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 updates_and_removes_entries() { let mut cache = LruCache::new(2); assert_eq!(cache.put("a", 1), None); assert_eq!(cache.put("a", 7), Some(1)); assert_eq!(cache.remove(&"a"), Some(7)); assert!(cache.is_empty()); let mut disabled = LruCache::new(0); disabled.put("ignored", 1); assert!(disabled.is_empty()); } }