use std::collections::HashMap; use std::hash::Hash; struct Node { value: V, prev: Option, next: Option, } /// A small LRU cache with fixed capacity. /// /// The head of the linked list is the most recently used entry; the tail is the /// least recently used entry and is evicted first when capacity is exceeded. pub struct LruCache { capacity: usize, map: HashMap>, head: Option, tail: Option, } impl LruCache where K: Eq + Hash + Clone, { pub fn new(capacity: usize) -> Self { Self { capacity, map: HashMap::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() } pub fn contains_key(&self, key: &K) -> bool { self.map.contains_key(key) } pub fn clear(&mut self) { self.map.clear(); self.head = None; self.tail = None; } pub fn get(&mut self, key: &K) -> Option<&V> { if self.map.contains_key(key) { self.move_to_front(key); self.map.get(key).map(|node| &node.value) } else { None } } pub fn put(&mut self, key: K, value: V) -> Option { if self.capacity == 0 { return None; } if self.map.contains_key(&key) { let old = { let node = self.map.get_mut(&key).expect("key was just checked"); std::mem::replace(&mut node.value, value) }; self.move_to_front(&key); return Some(old); } self.insert_front(key, value); if self.map.len() > self.capacity { self.pop_lru(); } None } pub fn remove(&mut self, key: &K) -> Option { self.detach(key); self.map.remove(key).map(|node| node.value) } fn insert_front(&mut self, key: K, value: V) { let old_head = self.head.clone(); self.map.insert( key.clone(), Node { value, prev: None, next: old_head.clone(), }, ); if let Some(head_key) = old_head { if let Some(head) = self.map.get_mut(&head_key) { head.prev = Some(key.clone()); } } else { self.tail = Some(key.clone()); } self.head = Some(key); } fn move_to_front(&mut self, key: &K) { if self.head.as_ref() == Some(key) { return; } self.detach(key); let old_head = self.head.clone(); if let Some(node) = self.map.get_mut(key) { node.prev = None; node.next = old_head.clone(); } if let Some(head_key) = old_head { if let Some(head) = self.map.get_mut(&head_key) { head.prev = Some(key.clone()); } } else { self.tail = Some(key.clone()); } self.head = Some(key.clone()); } fn detach(&mut self, key: &K) { let (prev, next) = match self.map.get(key) { Some(node) => (node.prev.clone(), node.next.clone()), None => return, }; match prev.as_ref() { Some(prev_key) => { if let Some(prev_node) = self.map.get_mut(prev_key) { prev_node.next = next.clone(); } } None => { self.head = next.clone(); } } match next.as_ref() { Some(next_key) => { if let Some(next_node) = self.map.get_mut(next_key) { next_node.prev = prev.clone(); } } None => { self.tail = prev.clone(); } } if let Some(node) = self.map.get_mut(key) { node.prev = None; node.next = None; } } fn pop_lru(&mut self) -> Option<(K, V)> { let key = self.tail.clone()?; self.detach(&key); self.map.remove_entry(&key).map(|(key, node)| (key, node.value)) } } #[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_existing_key_refreshes_it() { let mut cache = LruCache::new(2); cache.put(1, "one"); cache.put(2, "two"); assert_eq!(cache.put(1, "ONE"), Some("one")); cache.put(3, "three"); assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&1), Some(&"ONE")); assert_eq!(cache.get(&3), Some(&"three")); } }