use std::collections::HashMap;
pub struct Cacher<F, U, V>
where
F: Fn(U) -> V,
U: std::cmp::Eq + std::hash::Hash + Copy,
V: Copy,
{
calculation: F,
values: HashMap<U, V>,
}
impl<F, U, V> Cacher<F, U, V>
where
F: Fn(U) -> V,
U: std::cmp::Eq + std::hash::Hash + Copy,
V: Copy,
{
pub fn new(calculation: F) -> Cacher<F, U, V> {
Cacher {
calculation,
values: HashMap::new(),
}
}
pub fn call(&mut self, arg: U) -> V {
let calc = &self.calculation;
*self.values.entry(arg).or_insert_with_key(|&key| calc(key))
}
pub fn call_and_replace(&mut self, arg: U) -> V {
let new_val = (self.calculation)(arg);
self.values.insert(arg, new_val);
new_val
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cacher_basically_works() {
let mut word_len = Cacher::new(|word: &str| word.len());
let hello = word_len.call("hello");
assert_eq!(hello, 5);
assert_eq!(word_len.values.len(), 1);
let mut test_map = HashMap::new();
test_map.insert("hello", 5);
assert_eq!(word_len.values, test_map);
word_len.call("hello");
assert_eq!(word_len.values, test_map);
word_len.call("wazzup");
test_map.insert("wazzup", 6);
assert_eq!(word_len.values, test_map);
}
#[test]
fn test_cacher_speed() {
let mut func = Cacher::new(|x| {
std::thread::sleep(std::time::Duration::from_millis(100));
x * x
});
for _ in 0..6000 {
assert_eq!(25, func.call(5));
}
}
#[test]
fn test_call_and_replace() {
use std::time::Instant;
let mut func = Cacher::new(|_param: usize| Instant::now());
let first_instant = func.call(0);
let lookup_instant = func.call(0);
assert_eq!(first_instant, lookup_instant);
assert_eq!(1, func.values.len());
let second_instant = func.call_and_replace(0);
assert_eq!(1, func.values.len());
assert_ne!(second_instant, lookup_instant);
}
}