contest_algorithms/caching.rs
1//! Basic Cacher struct which stores a closure and a hashmap.
2//! The hasmap stores key value pairs representing previous
3//! function calls.
4//!
5//! When the Cacher function is run, it first does a lookup
6//! to see if the value has already been calculated. If it has,
7//! it returns that value. If it hasn't, it calculates the value,
8//! adds it to the hashmap, and returns it.
9
10use std::collections::HashMap;
11
12/// The Cacher struct (Memoization) stores a function and a Hashmap.
13/// The HashMap keeps track of previous input and output for the function so
14/// that it only ever has to be called once per input. Use for expensive functions.
15pub struct Cacher<F, U, V>
16where
17 F: Fn(U) -> V,
18 U: std::cmp::Eq + std::hash::Hash + Copy,
19 V: Copy,
20{
21 calculation: F,
22 values: HashMap<U, V>,
23}
24
25impl<F, U, V> Cacher<F, U, V>
26where
27 F: Fn(U) -> V,
28 U: std::cmp::Eq + std::hash::Hash + Copy,
29 V: Copy,
30{
31 /// Constuctor for the Casher
32 /// # Examples
33 /// ```
34 /// # use contest_algorithms::caching::Cacher;
35 /// let mut squared = Cacher::new(|n: u32| n*n);
36 /// ```
37 pub fn new(calculation: F) -> Cacher<F, U, V> {
38 Cacher {
39 calculation,
40 values: HashMap::new(),
41 }
42 }
43
44 /// Performs a lookup into the HashMap to see if the value has already
45 /// been calculated. If it has, returns the value. If it has not,
46 /// calls the function, stores the value, then returns the value.
47 /// # Examples
48 /// ```
49 /// # use contest_algorithms::caching::Cacher;
50 /// let mut squared = Cacher::new(|n: u32| n*n);
51 ///
52 /// // This is where we call the function
53 /// let sixteen = squared.call(4);
54 /// ```
55 // TODO: whenever Rust's Entry API gains the ability to take ownership of
56 // arg only when necessary, this method should follow the same practice.
57 // Also, Cacher should implement Fn(U)->V once this is possible.
58 pub fn call(&mut self, arg: U) -> V {
59 let calc = &self.calculation;
60 *self.values.entry(arg).or_insert_with_key(|&key| calc(key))
61 }
62
63 /// Calls the function without performing a lookup and replaces
64 /// the old return value with the new one, and returns it.
65 /// Potentially useful if the function reads from a file or RNG
66 /// whose state may have changed.
67 // TODO: if there's state, FnMut seems more appropriate.
68 pub fn call_and_replace(&mut self, arg: U) -> V {
69 let new_val = (self.calculation)(arg);
70 self.values.insert(arg, new_val);
71 new_val
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_cacher_basically_works() {
81 let mut word_len = Cacher::new(|word: &str| word.len());
82 let hello = word_len.call("hello");
83
84 // Test function returns correctly
85 assert_eq!(hello, 5);
86
87 // Test HashMap is correct length
88 assert_eq!(word_len.values.len(), 1);
89
90 // Test HashMap has correct value after one insert
91 let mut test_map = HashMap::new();
92 test_map.insert("hello", 5);
93 assert_eq!(word_len.values, test_map);
94
95 // Test HashMap has correct value after duplicate insert
96 word_len.call("hello");
97 assert_eq!(word_len.values, test_map);
98
99 // Test HashMap has correct values after unique input
100 word_len.call("wazzup");
101 test_map.insert("wazzup", 6);
102 assert_eq!(word_len.values, test_map);
103 }
104
105 #[test]
106 fn test_cacher_speed() {
107 // Simulate a function that takes 1 second to complete
108 let mut func = Cacher::new(|x| {
109 std::thread::sleep(std::time::Duration::from_millis(100));
110 x * x
111 });
112
113 // Would take 10 minutes without caching
114 for _ in 0..6000 {
115 assert_eq!(25, func.call(5));
116 }
117 }
118
119 #[test]
120 fn test_call_and_replace() {
121 use std::time::Instant;
122
123 let mut func = Cacher::new(|_param: usize| Instant::now());
124 let first_instant = func.call(0);
125 let lookup_instant = func.call(0);
126
127 assert_eq!(first_instant, lookup_instant);
128 assert_eq!(1, func.values.len());
129
130 let second_instant = func.call_and_replace(0);
131 assert_eq!(1, func.values.len());
132 assert_ne!(second_instant, lookup_instant);
133 }
134}