use std::collections::HashMap;
use std::hash::Hash;
use crate::numerics::comp::ApproxComparable;
use crate::caching::approximate_cache::ApproximateCache;
use super::linked_list::DoublyLinkedList;
use super::list_node::{Node, SharedNode};
pub struct BoundedLinearCache<K, V> {
max_capacity: usize,
map: HashMap<K, SharedNode<K, V>>,
list: DoublyLinkedList<K, V>,
tolerance: f32,
}
impl<K, V> ApproximateCache<K, V> for BoundedLinearCache<K, V>
where
K: ApproxComparable + Eq + Hash + Clone,
V: Clone,
{
fn find(&mut self, key: &K) -> Option<V> {
let matching = self
.map
.keys()
.find(|&k| key.roughly_matches(k, self.tolerance))?;
let node: SharedNode<K, V> = self.map.get(matching).cloned()?;
self.list.remove(node.clone());
self.list.add_to_head(node.clone());
return Some(node.borrow().value.clone());
}
fn insert(&mut self, key: K, value: V) {
if self.len() == self.max_capacity {
if let Some(tail) = self.list.remove_tail() {
self.map.remove(&tail.borrow().key);
}
}
let new_node = Node::new(key.clone(), value);
self.list.add_to_head(new_node.clone());
self.map.insert(key, new_node);
}
fn len(&self) -> usize {
self.map.len()
}
}
impl<K, V> BoundedLinearCache<K, V> {
pub fn new(max_capacity: usize, tolerance: f32) -> Self {
assert!(max_capacity > 0);
assert!(tolerance > 0.0);
Self {
max_capacity,
map: HashMap::new(),
list: DoublyLinkedList::new(),
tolerance,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_TOLERANCE: f32 = 1e-8;
#[test]
fn test_lru_cache_basic_operations() {
let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(2, TEST_TOLERANCE);
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)); }
#[test]
fn test_lru_cache_eviction_order() {
let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(3, TEST_TOLERANCE);
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)); }
#[test]
fn test_lru_cache_overwrite() {
let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(2, TEST_TOLERANCE);
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)); }
#[test]
fn test_lru_cache_capacity_one() {
let mut cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(1, TEST_TOLERANCE);
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)); }
#[test]
#[should_panic]
fn test_lru_cache_empty() {
let _cache: BoundedLinearCache<i16, i16> = BoundedLinearCache::new(0, TEST_TOLERANCE);
}
}