proximipy/caching/bounded/
bounded_linear_cache.rs1use std::collections::HashMap;
2use std::hash::Hash;
3
4use crate::numerics::comp::ApproxComparable;
5
6use crate::caching::approximate_cache::ApproximateCache;
7
8use super::linked_list::DoublyLinkedList;
9use super::list_node::{Node, SharedNode};
10
11pub struct BoundedLinearCache<K, V> {
47 max_capacity: usize,
48 map: HashMap<K, SharedNode<K, V>>,
49 list: DoublyLinkedList<K, V>,
50 tolerance: f32,
51}
52
53impl<K, V> ApproximateCache<K, V> for BoundedLinearCache<K, V>
54where
55 K: ApproxComparable + Eq + Hash + Clone,
56 V: Clone,
57{
58 fn find(&mut self, key: &K) -> Option<V> {
59 let matching = self
60 .map
61 .keys()
62 .find(|&k| key.roughly_matches(k, self.tolerance))?;
63 let node: SharedNode<K, V> = self.map.get(matching).cloned()?;
64 self.list.remove(node.clone());
65 self.list.add_to_head(node.clone());
66 return Some(node.borrow().value.clone());
67 }
68
69 fn insert(&mut self, key: K, value: V) {
70 if self.len() == self.max_capacity {
71 if let Some(tail) = self.list.remove_tail() {
72 self.map.remove(&tail.borrow().key);
73 }
74 }
75 let new_node = Node::new(key.clone(), value);
76 self.list.add_to_head(new_node.clone());
77 self.map.insert(key, new_node);
78 }
79
80 fn len(&self) -> usize {
81 self.map.len()
82 }
83}
84
85impl<K, V> BoundedLinearCache<K, V> {
86 pub fn new(max_capacity: usize, tolerance: f32) -> Self {
87 assert!(max_capacity > 0);
88 assert!(tolerance > 0.0);
89 Self {
90 max_capacity,
91 map: HashMap::new(),
92 list: DoublyLinkedList::new(),
93 tolerance,
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 const TEST_TOLERANCE: f32 = 1e-8;
103 #[test]
104 fn test_lru_cache_basic_operations() {
105 let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(2, TEST_TOLERANCE);
106 cache.insert(1, 1); cache.insert(2, 2); assert_eq!(cache.find(&1), Some(1)); cache.insert(3, 3); assert_eq!(cache.find(&2), None); cache.insert(4, 4); assert_eq!(cache.find(&1), None); assert_eq!(cache.find(&3), Some(3)); assert_eq!(cache.find(&4), Some(4)); }
116
117 #[test]
118 fn test_lru_cache_eviction_order() {
119 let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(3, TEST_TOLERANCE);
120 cache.insert(1, 1); cache.insert(2, 2); cache.insert(3, 3); cache.find(&1); cache.insert(4, 4); assert_eq!(cache.find(&2), None); assert_eq!(cache.find(&3), Some(3)); assert_eq!(cache.find(&4), Some(4)); assert_eq!(cache.find(&1), Some(1)); }
130
131 #[test]
132 fn test_lru_cache_overwrite() {
133 let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(2, TEST_TOLERANCE);
134 cache.insert(1, 1); cache.insert(2, 2); cache.insert(1, 10); assert_eq!(cache.find(&1), Some(10)); cache.insert(3, 3); assert_eq!(cache.find(&2), None); assert_eq!(cache.find(&3), Some(3)); }
142
143 #[test]
144 fn test_lru_cache_capacity_one() {
145 let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(1, TEST_TOLERANCE);
146 cache.insert(1, 1); assert_eq!(cache.find(&1), Some(1)); cache.insert(2, 2); assert_eq!(cache.find(&1), None); assert_eq!(cache.find(&2), Some(2)); }
152
153 #[test]
154 #[should_panic]
155 fn test_lru_cache_empty() {
156 let _cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(0, TEST_TOLERANCE);
157 }
158}