1use std::collections::HashMap;
12use std::hash::Hash;
13
14pub struct LruCache<K: Eq + Hash + Clone, V: Clone> {
18 capacity: usize,
19 map: HashMap<K, (V, u64)>,
20 tick: u64,
22}
23
24impl<K: Eq + Hash + Clone, V: Clone> LruCache<K, V> {
25 pub fn new(capacity: usize) -> Self {
26 assert!(capacity >= 1, "capacity must be >= 1");
27 Self { capacity, map: HashMap::with_capacity(capacity), tick: 0 }
28 }
29
30 pub fn capacity(&self) -> usize {
31 self.capacity
32 }
33
34 pub fn len(&self) -> usize {
35 self.map.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.map.is_empty()
40 }
41
42 pub fn contains(&self, key: &K) -> bool {
43 self.map.contains_key(key)
44 }
45
46 pub fn get(&mut self, key: &K) -> Option<V> {
48 let v = self.map.get_mut(key).map(|(v, last)| {
49 self.tick += 1;
50 *last = self.tick;
51 v.clone()
52 });
53 v
54 }
55
56 pub fn put(&mut self, key: K, value: V) -> Option<V> {
59 self.tick += 1;
60 if self.map.contains_key(&key) {
61 let (slot, last) = self.map.get_mut(&key).expect("checked above");
62 *slot = value;
63 *last = self.tick;
64 return None;
65 }
66 if self.map.len() >= self.capacity {
67 if let Some((evict_k, _)) =
69 self.map.iter().min_by_key(|(_, (_, last))| *last).map(|(k, _)| (k.clone(), ()))
70 {
71 let (evicted, _) = self.map.remove(&evict_k).expect("just found");
72 self.map.insert(key, (value, self.tick));
73 return Some(evicted);
74 }
75 }
76 self.map.insert(key, (value, self.tick));
77 None
78 }
79
80 pub fn remove(&mut self, key: &K) -> Option<V> {
81 self.map.remove(key).map(|(v, _)| v)
82 }
83
84 pub fn clear(&mut self) {
85 self.map.clear();
86 self.tick = 0;
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn put_and_get() {
96 let mut c = LruCache::<&'static str, i32>::new(3);
97 assert!(c.put("a", 1).is_none());
98 assert!(c.put("b", 2).is_none());
99 assert_eq!(c.get(&"a"), Some(1));
100 assert_eq!(c.get(&"b"), Some(2));
101 assert_eq!(c.get(&"c"), None);
102 assert_eq!(c.len(), 2);
103 }
104
105 #[test]
106 fn lru_eviction_drops_least_recently_used() {
107 let mut c = LruCache::<&'static str, i32>::new(2);
108 c.put("a", 1);
109 c.put("b", 2);
110 let _ = c.get(&"a"); let evicted = c.put("c", 3); assert_eq!(evicted, Some(2));
113 assert!(!c.contains(&"b"));
114 assert!(c.contains(&"a"));
115 assert!(c.contains(&"c"));
116 }
117
118 #[test]
119 fn put_existing_key_updates_value_no_evict() {
120 let mut c = LruCache::<&'static str, i32>::new(2);
121 c.put("a", 1);
122 c.put("b", 2);
123 let evicted = c.put("a", 99);
124 assert!(evicted.is_none());
125 assert_eq!(c.get(&"a"), Some(99));
126 assert_eq!(c.len(), 2);
127 }
128
129 #[test]
130 fn remove_drops_entry() {
131 let mut c = LruCache::<&'static str, i32>::new(2);
132 c.put("a", 1);
133 let r = c.remove(&"a");
134 assert_eq!(r, Some(1));
135 assert!(c.is_empty());
136 }
137
138 #[test]
139 fn clear_resets_state() {
140 let mut c = LruCache::<&'static str, i32>::new(2);
141 c.put("a", 1);
142 c.put("b", 2);
143 c.clear();
144 assert!(c.is_empty());
145 }
146
147 #[test]
148 #[should_panic]
149 fn zero_capacity_panics() {
150 let _: LruCache<&'static str, i32> = LruCache::new(0);
151 }
152}