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}