fliplru/lib.rs
1#![no_std]
2
3use core::borrow::Borrow;
4use core::hash::Hash;
5use core::num::NonZeroUsize;
6use core::{cmp, mem};
7use hashbrown::HashMap;
8use polonius_the_crab::{polonius, polonius_return};
9
10/// An LRU Cache
11pub struct LruCache<K, V> {
12 l1_map: HashMap<K, V>,
13 l2_map: HashMap<K, V>,
14 cap: NonZeroUsize,
15 flips: usize,
16}
17
18impl<K: Hash + Eq, V> LruCache<K, V> {
19 /// Creates a new LRU Cache that holds `cap` items.
20 /// It can fetch upto the last `cap*2` items, but only
21 /// the last `cap` items is guaranteed to be in the cache.
22 ///
23 /// When the cache is full (reached cap items), then a "flip" occurs internally,
24 /// where the full cache is backed up and an empty cache is brought in its place.
25 /// Then as cache misses occur, the cache gets populated internally from the backup
26 /// cache if the item is found there or a miss is reported to the user.
27 ///
28 /// # Example
29 ///
30 /// ```
31 /// use fliplru::LruCache;
32 /// use std::num::NonZeroUsize;
33 /// let mut cache: LruCache<isize, &str> = LruCache::new(NonZeroUsize::new(10).unwrap());
34 /// ```
35 pub fn new(cap: NonZeroUsize) -> LruCache<K, V> {
36 LruCache {
37 l1_map: HashMap::with_capacity(cap.into()),
38 l2_map: HashMap::with_capacity(cap.into()),
39 cap,
40 flips: 0,
41 }
42 }
43
44 /// Returns a reference to the value of the key in the cache or `None` if it is not
45 /// present in the cache.
46 ///
47 /// # Example
48 ///
49 /// ```
50 /// use fliplru::LruCache;
51 /// use std::num::NonZeroUsize;
52 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
53 ///
54 /// cache.put(1, "a");
55 /// cache.put(2, "b");
56 /// cache.put(2, "c");
57 /// cache.put(3, "d");
58 ///
59 /// assert_eq!(cache.get(&2), Some(&"c"));
60 /// assert_eq!(cache.get(&3), Some(&"d"));
61 /// ```
62 pub fn get<'a, Q>(&'a mut self, k: &Q) -> Option<&'a V>
63 where
64 K: Borrow<Q>,
65 Q: Hash + Eq + ?Sized,
66 {
67 let mut this = self;
68 polonius!(|this| -> Option<&'polonius V> {
69 if let Some(v) = this.l1_map.get(k) {
70 polonius_return!(Some(v));
71 }
72 });
73
74 match this.l2_map.remove_entry(k) {
75 Some((rk, rv)) => {
76 this.put(rk, rv);
77 this.l1_map.get(k)
78 }
79 None => None,
80 }
81 }
82
83 /// Returns a mutable reference to the value of the key in the cache or `None` if it
84 /// is not present in the cache. Moves the key to the l1_map if it exists in the l2_map.
85 ///
86 /// # Example
87 ///
88 /// ```
89 /// use fliplru::LruCache;
90 /// use std::num::NonZeroUsize;
91 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
92 ///
93 /// cache.put("apple", 8);
94 /// cache.put("banana", 4);
95 /// cache.put("banana", 6);
96 /// cache.put("pear", 2);
97 ///
98 /// assert_eq!(cache.get_mut(&"apple"), Some(&mut 8));
99 /// assert_eq!(cache.get_mut(&"banana"), Some(&mut 6));
100 /// assert_eq!(cache.get_mut(&"pear"), Some(&mut 2));
101 /// ```
102 pub fn get_mut<'a, Q>(&'a mut self, k: &Q) -> Option<&'a mut V>
103 where
104 K: Borrow<Q>,
105 Q: Hash + Eq + ?Sized,
106 {
107 let mut this = self;
108 polonius!(|this| -> Option<&'polonius mut V> {
109 if let Some(v) = this.l1_map.get_mut(k) {
110 polonius_return!(Some(v));
111 }
112 });
113
114 match this.l2_map.remove_entry(k) {
115 Some((rk, rv)) => {
116 this.put(rk, rv);
117 this.l1_map.get_mut(k)
118 }
119 None => None,
120 }
121 }
122
123 /// Puts a key-value pair into cache. If the key already exists in the cache, then it updates
124 /// the key's value and returns the old value. Otherwise, `None` is returned.
125 ///
126 /// # Example
127 ///
128 /// ```
129 /// use fliplru::LruCache;
130 /// use std::num::NonZeroUsize;
131 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
132 ///
133 /// assert_eq!(None, cache.put(1, "a"));
134 /// assert_eq!(None, cache.put(2, "b"));
135 /// assert_eq!(Some("b"), cache.put(2, "beta"));
136 ///
137 /// assert_eq!(cache.get(&1), Some(&"a"));
138 /// assert_eq!(cache.get(&2), Some(&"beta"));
139 /// ```
140 pub fn put(&mut self, k: K, v: V) -> Option<V> {
141 if self.l1_map.len() == self.cap.into() {
142 mem::swap(&mut self.l2_map, &mut self.l1_map);
143 let _ = mem::replace(&mut self.l1_map, HashMap::with_capacity(self.cap.into()));
144 self.flips += 1;
145 }
146 // invalidate any existing entry in L2 cache
147 let ov = self.l2_map.remove(&k);
148 match self.l1_map.insert(k, v) {
149 Some(l1_v) => Some(l1_v),
150 None => ov,
151 }
152 }
153
154 /// Returns the maximum number of key-value pairs the cache can hold.
155 ///
156 /// # Example
157 ///
158 /// ```
159 /// use fliplru::LruCache;
160 /// use std::num::NonZeroUsize;
161 /// let mut cache: LruCache<isize, &str> = LruCache::new(NonZeroUsize::new(2).unwrap());
162 /// assert_eq!(cache.cap().get(), 2);
163 /// ```
164 pub fn cap(&self) -> NonZeroUsize {
165 self.cap
166 }
167
168 /// Returns the number of key-value pairs that are currently in the the cache.
169 ///
170 /// # Example
171 ///
172 /// ```
173 /// use fliplru::LruCache;
174 /// use std::num::NonZeroUsize;
175 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
176 /// assert_eq!(cache.len(), 0);
177 ///
178 /// cache.put(1, "a");
179 /// assert_eq!(cache.len(), 1);
180 ///
181 /// cache.put(2, "b");
182 /// assert_eq!(cache.len(), 2);
183 ///
184 /// cache.put(3, "c");
185 /// assert_eq!(cache.len(), 2);
186 /// ```
187 pub fn len(&self) -> usize {
188 cmp::min(self.l1_map.len() + self.l2_map.len(), self.cap().into())
189 }
190
191 /// Returns a bool indicating whether the cache is empty or not.
192 ///
193 /// # Example
194 ///
195 /// ```
196 /// use fliplru::LruCache;
197 /// use std::num::NonZeroUsize;
198 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
199 /// assert!(cache.is_empty());
200 ///
201 /// cache.put(1, "a");
202 /// assert!(!cache.is_empty());
203 /// ```
204 pub fn is_empty(&self) -> bool {
205 self.l1_map.len() == 0 && self.l2_map.len() == 0
206 }
207
208 /// Returns metric on the number of times the cache became full.
209 ///
210 /// # Example
211 ///
212 /// ```
213 /// use fliplru::LruCache;
214 /// use std::num::NonZeroUsize;
215 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
216 ///
217 /// for i in 0..5 {
218 /// cache.put(i, i);
219 /// }
220 /// for i in 0..20 {
221 /// cache.get(&(i % 5));
222 /// }
223 /// assert_eq!(cache.get_flips(), 8);
224 /// ```
225
226 pub fn get_flips(&self) -> usize {
227 self.flips
228 }
229
230 /// Reset the flip metric.
231 ///
232 /// # Example
233 ///
234 /// ```
235 /// use fliplru::LruCache;
236 /// use std::num::NonZeroUsize;
237 /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
238 ///
239 /// for i in 0..5 {
240 /// cache.put(i, i);
241 /// }
242 /// for i in 0..20 {
243 /// cache.get(&(i % 5));
244 /// }
245 /// assert_eq!(cache.get_flips(), 8);
246 /// cache.reset();
247 /// assert_eq!(cache.get_flips(), 0);
248 /// ```
249 pub fn reset(&mut self) {
250 self.flips = 0;
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::LruCache;
257 use core::{fmt::Debug, num::NonZeroUsize};
258
259 fn assert_opt_eq<V: PartialEq + Debug>(opt: Option<&V>, v: V) {
260 assert!(opt.is_some());
261 assert_eq!(opt.unwrap(), &v);
262 }
263
264 #[test]
265 fn test_put_and_get() {
266 let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
267 assert!(cache.is_empty());
268 assert_eq!(cache.get_flips(), 0);
269
270 assert_eq!(cache.put("apple", "red"), None);
271 assert_eq!(cache.put("banana", "yellow"), None);
272
273 assert_eq!(cache.cap().get(), 2);
274 assert_eq!(cache.len(), 2);
275 assert_eq!(cache.get_flips(), 0);
276 assert!(!cache.is_empty());
277 assert_opt_eq(cache.get(&"apple"), "red");
278 assert_opt_eq(cache.get(&"banana"), "yellow");
279 }
280
281 #[test]
282 fn test_put_update() {
283 let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
284
285 assert_eq!(cache.put("apple", "red"), None);
286 assert_eq!(cache.put("apple", "green"), Some("red"));
287
288 assert_eq!(cache.len(), 1);
289 assert_opt_eq(cache.get(&"apple"), "green");
290 }
291
292 #[test]
293 fn test_l2() {
294 let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
295
296 assert_eq!(cache.get_flips(), 0);
297 assert_eq!(cache.put("apple", "red"), None);
298 assert_eq!(cache.put("banana", "yellow"), None);
299 assert_eq!(cache.put("pear", "green"), None);
300 assert_eq!(cache.get_flips(), 1);
301
302 // This is retrieved from the overflow (L2 cache)
303 assert_opt_eq(cache.get(&"apple"), "red");
304 assert_opt_eq(cache.get(&"banana"), "yellow");
305 assert_opt_eq(cache.get(&"pear"), "green");
306 assert_eq!(cache.get_flips(), 2);
307
308 // apple is no longer in both the caches
309 assert_eq!(cache.put("apple", "green"), None);
310 assert_eq!(cache.put("tomato", "red"), None);
311 assert_eq!(cache.get_flips(), 3);
312
313 assert_opt_eq(cache.get(&"pear"), "green");
314 assert_opt_eq(cache.get(&"apple"), "green");
315 assert_opt_eq(cache.get(&"tomato"), "red");
316 assert_eq!(cache.get_flips(), 5);
317 }
318
319 #[test]
320 fn test_max_cache_len() {
321 let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
322
323 assert_eq!(cache.put("apple", "red"), None);
324 assert_eq!(cache.put("banana", "yellow"), None);
325 assert_eq!(cache.put("pear", "green"), None);
326 assert_eq!(cache.put("tomato", "red"), None);
327 assert_eq!(cache.get_flips(), 1);
328
329 // Could retrieve `cap*2` oldest item, i.e., the 4th oldest item.
330 assert_opt_eq(cache.get(&"apple"), "red");
331 assert_eq!(cache.get_flips(), 2);
332
333 // Could not retrieve `cap+1` oldest item, i.e., the 3rd oldest item, showing that only the
334 // first `cap` items is guaranteed to be in the cache.
335 assert_eq!(cache.get(&"banana"), None);
336 assert_eq!(cache.get_flips(), 2);
337
338 cache.reset();
339 assert_eq!(cache.get_flips(), 0);
340 }
341
342 #[test]
343 fn test_cache_under_capacity() {
344 let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
345 for i in 0..5 {
346 cache.put(i, i);
347 }
348 for i in 0..20 {
349 cache.get(&(i % 5));
350 }
351
352 assert_eq!(cache.get_flips(), 8);
353 }
354
355 #[test]
356 fn test_cache_over_capacity() {
357 let mut cache = LruCache::new(NonZeroUsize::new(5).unwrap());
358 for i in 0..5 {
359 cache.put(i, i);
360 }
361 for i in 0..20 {
362 cache.get(&(i % 5));
363 }
364
365 assert_eq!(cache.get_flips(), 0);
366 }
367}