| 1 | use alloc::collections::VecDeque; |
| 2 | use core::borrow::Borrow; |
| 3 | use core::hash::Hash; |
| 4 | |
| 5 | use crate::hash_map::{Entry, HashMap}; |
| 6 | |
| 7 | /// A HashMap-alike, which never gets larger than a specified |
| 8 | /// capacity, and evicts the oldest insertion to maintain this. |
| 9 | /// |
| 10 | /// The requested capacity may be rounded up by the underlying |
| 11 | /// collections. This implementation uses all the allocated |
| 12 | /// storage. |
| 13 | /// |
| 14 | /// This is inefficient: it stores keys twice. |
| 15 | pub(crate) struct LimitedCache<K: Clone + Hash + Eq, V> { |
| 16 | map: HashMap<K, V>, |
| 17 | |
| 18 | // first item is the oldest key |
| 19 | oldest: VecDeque<K>, |
| 20 | } |
| 21 | |
| 22 | impl<K, V> LimitedCache<K, V> |
| 23 | where |
| 24 | K: Eq + Hash + Clone + core::fmt::Debug, |
| 25 | V: Default, |
| 26 | { |
| 27 | pub(crate) fn get_or_insert_default_and_edit(&mut self, k: K, edit: impl FnOnce(&mut V)) { |
| 28 | let inserted_new_item = match self.map.entry(k) { |
| 29 | Entry::Occupied(value) => { |
| 30 | edit(value.into_mut()); |
| 31 | false |
| 32 | } |
| 33 | entry @ Entry::Vacant(_) => { |
| 34 | self.oldest |
| 35 | .push_back(entry.key().clone()); |
| 36 | edit(entry.or_insert_with(V::default)); |
| 37 | true |
| 38 | } |
| 39 | }; |
| 40 | |
| 41 | // ensure next insertion does not require a realloc |
| 42 | if inserted_new_item && self.oldest.capacity() == self.oldest.len() { |
| 43 | if let Some(oldest_key) = self.oldest.pop_front() { |
| 44 | self.map.remove(&oldest_key); |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | pub(crate) fn get_mut<Q: Hash + Eq + ?Sized>(&mut self, k: &Q) -> Option<&mut V> |
| 50 | where |
| 51 | K: Borrow<Q>, |
| 52 | { |
| 53 | self.map.get_mut(k) |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | impl<K, V> LimitedCache<K, V> |
| 58 | where |
| 59 | K: Eq + Hash + Clone + core::fmt::Debug, |
| 60 | V: Default, |
| 61 | { |
| 62 | /// Create a new LimitedCache with the given rough capacity. |
| 63 | pub(crate) fn new(capacity_order_of_magnitude: usize) -> Self { |
| 64 | Self { |
| 65 | map: HashMap::with_capacity(capacity_order_of_magnitude), |
| 66 | oldest: VecDeque::with_capacity(capacity_order_of_magnitude), |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | pub(crate) fn insert(&mut self, k: K, v: V) { |
| 71 | let inserted_new_item = match self.map.entry(k) { |
| 72 | Entry::Occupied(mut old) => { |
| 73 | // Note: does not freshen entry in `oldest` |
| 74 | old.insert(v); |
| 75 | false |
| 76 | } |
| 77 | |
| 78 | entry @ Entry::Vacant(_) => { |
| 79 | self.oldest |
| 80 | .push_back(entry.key().clone()); |
| 81 | entry.or_insert(v); |
| 82 | true |
| 83 | } |
| 84 | }; |
| 85 | |
| 86 | // ensure next insertion does not require a realloc |
| 87 | if inserted_new_item && self.oldest.capacity() == self.oldest.len() { |
| 88 | if let Some(oldest_key) = self.oldest.pop_front() { |
| 89 | self.map.remove(&oldest_key); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | pub(crate) fn get<Q: Hash + Eq + ?Sized>(&self, k: &Q) -> Option<&V> |
| 95 | where |
| 96 | K: Borrow<Q>, |
| 97 | { |
| 98 | self.map.get(k) |
| 99 | } |
| 100 | |
| 101 | pub(crate) fn remove<Q: Hash + Eq + ?Sized>(&mut self, k: &Q) -> Option<V> |
| 102 | where |
| 103 | K: Borrow<Q>, |
| 104 | { |
| 105 | let value = self.map.remove(k)?; |
| 106 | |
| 107 | // O(N) search, followed by O(N) removal |
| 108 | if let Some(index) = self |
| 109 | .oldest |
| 110 | .iter() |
| 111 | .position(|item| item.borrow() == k) |
| 112 | { |
| 113 | self.oldest.remove(index); |
| 114 | } |
| 115 | |
| 116 | Some(value) |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | #[cfg (test)] |
| 121 | mod tests { |
| 122 | use std::prelude::v1::*; |
| 123 | |
| 124 | type Test = super::LimitedCache<String, usize>; |
| 125 | |
| 126 | #[test ] |
| 127 | fn test_updates_existing_item() { |
| 128 | let mut t = Test::new(3); |
| 129 | t.insert("abc" .into(), 1); |
| 130 | t.insert("abc" .into(), 2); |
| 131 | assert_eq!(t.get("abc" ), Some(&2)); |
| 132 | } |
| 133 | |
| 134 | #[test ] |
| 135 | fn test_evicts_oldest_item() { |
| 136 | let mut t = Test::new(3); |
| 137 | t.insert("abc" .into(), 1); |
| 138 | t.insert("def" .into(), 2); |
| 139 | t.insert("ghi" .into(), 3); |
| 140 | |
| 141 | assert_eq!(t.get("abc" ), None); |
| 142 | assert_eq!(t.get("def" ), Some(&2)); |
| 143 | assert_eq!(t.get("ghi" ), Some(&3)); |
| 144 | } |
| 145 | |
| 146 | #[test ] |
| 147 | fn test_evicts_second_oldest_item_if_first_removed() { |
| 148 | let mut t = Test::new(3); |
| 149 | t.insert("abc" .into(), 1); |
| 150 | t.insert("def" .into(), 2); |
| 151 | |
| 152 | assert_eq!(t.remove("abc" ), Some(1)); |
| 153 | |
| 154 | t.insert("ghi" .into(), 3); |
| 155 | t.insert("jkl" .into(), 4); |
| 156 | |
| 157 | assert_eq!(t.get("abc" ), None); |
| 158 | assert_eq!(t.get("def" ), None); |
| 159 | assert_eq!(t.get("ghi" ), Some(&3)); |
| 160 | assert_eq!(t.get("jkl" ), Some(&4)); |
| 161 | } |
| 162 | |
| 163 | #[test ] |
| 164 | fn test_evicts_after_second_oldest_item_removed() { |
| 165 | let mut t = Test::new(3); |
| 166 | t.insert("abc" .into(), 1); |
| 167 | t.insert("def" .into(), 2); |
| 168 | |
| 169 | assert_eq!(t.remove("def" ), Some(2)); |
| 170 | assert_eq!(t.get("abc" ), Some(&1)); |
| 171 | |
| 172 | t.insert("ghi" .into(), 3); |
| 173 | t.insert("jkl" .into(), 4); |
| 174 | |
| 175 | assert_eq!(t.get("abc" ), None); |
| 176 | assert_eq!(t.get("def" ), None); |
| 177 | assert_eq!(t.get("ghi" ), Some(&3)); |
| 178 | assert_eq!(t.get("jkl" ), Some(&4)); |
| 179 | } |
| 180 | |
| 181 | #[test ] |
| 182 | fn test_removes_all_items() { |
| 183 | let mut t = Test::new(3); |
| 184 | t.insert("abc" .into(), 1); |
| 185 | t.insert("def" .into(), 2); |
| 186 | |
| 187 | assert_eq!(t.remove("def" ), Some(2)); |
| 188 | assert_eq!(t.remove("abc" ), Some(1)); |
| 189 | |
| 190 | t.insert("ghi" .into(), 3); |
| 191 | t.insert("jkl" .into(), 4); |
| 192 | t.insert("mno" .into(), 5); |
| 193 | |
| 194 | assert_eq!(t.get("abc" ), None); |
| 195 | assert_eq!(t.get("def" ), None); |
| 196 | assert_eq!(t.get("ghi" ), None); |
| 197 | assert_eq!(t.get("jkl" ), Some(&4)); |
| 198 | assert_eq!(t.get("mno" ), Some(&5)); |
| 199 | } |
| 200 | |
| 201 | #[test ] |
| 202 | fn test_inserts_many_items() { |
| 203 | let mut t = Test::new(3); |
| 204 | |
| 205 | for _ in 0..10000 { |
| 206 | t.insert("abc" .into(), 1); |
| 207 | t.insert("def" .into(), 2); |
| 208 | t.insert("ghi" .into(), 3); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | #[test ] |
| 213 | fn test_get_or_insert_default_and_edit_evicts_old_items_to_meet_capacity() { |
| 214 | let mut t = Test::new(3); |
| 215 | |
| 216 | t.get_or_insert_default_and_edit("abc" .into(), |v| *v += 1); |
| 217 | t.get_or_insert_default_and_edit("def" .into(), |v| *v += 2); |
| 218 | |
| 219 | // evicts "abc" |
| 220 | t.get_or_insert_default_and_edit("ghi" .into(), |v| *v += 3); |
| 221 | assert_eq!(t.get("abc" ), None); |
| 222 | |
| 223 | // evicts "def" |
| 224 | t.get_or_insert_default_and_edit("jkl" .into(), |v| *v += 4); |
| 225 | assert_eq!(t.get("def" ), None); |
| 226 | |
| 227 | // evicts "ghi" |
| 228 | t.get_or_insert_default_and_edit("abc" .into(), |v| *v += 5); |
| 229 | assert_eq!(t.get("ghi" ), None); |
| 230 | |
| 231 | // evicts "jkl" |
| 232 | t.get_or_insert_default_and_edit("def" .into(), |v| *v += 6); |
| 233 | |
| 234 | assert_eq!(t.get("abc" ), Some(&5)); |
| 235 | assert_eq!(t.get("def" ), Some(&6)); |
| 236 | assert_eq!(t.get("ghi" ), None); |
| 237 | assert_eq!(t.get("jkl" ), None); |
| 238 | } |
| 239 | |
| 240 | #[test ] |
| 241 | fn test_get_or_insert_default_and_edit_edits_existing_item() { |
| 242 | let mut t = Test::new(3); |
| 243 | |
| 244 | t.get_or_insert_default_and_edit("abc" .into(), |v| *v += 1); |
| 245 | t.get_or_insert_default_and_edit("abc" .into(), |v| *v += 2); |
| 246 | t.get_or_insert_default_and_edit("abc" .into(), |v| *v += 3); |
| 247 | |
| 248 | assert_eq!(t.get("abc" ), Some(&6)); |
| 249 | } |
| 250 | } |
| 251 | |