From b6a408eb8944df04d6c6e876a9a00f6c425f47f1 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Wed, 25 Aug 2021 08:25:30 +0200 Subject: [PATCH] Closures and Cacher object --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ src/main.rs | 21 +++++++++++++++++++++ tests/test_lib.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b9ee526..fe3b71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use std::fs::File; use std::io::prelude::*; +use std::collections::HashMap; pub fn read_file(path: &str) -> Result { let mut s = String::new(); @@ -48,3 +49,39 @@ pub fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { y } } + +/// Wrap and cache an expensive calculation +/// +/// This calls a closure just once for every distinct argument. Any subsequent +/// call to `.value()` with the same argument uses the cached value. +pub struct Cacher +where + T: Fn(A) -> V, + A: Eq + Copy + std::hash::Hash, + V: Copy, +{ + calc: T, + values: HashMap, +} + +impl Cacher +where + T: Fn(A) -> V, + A: Eq + Copy + std::hash::Hash, + V: Copy, +{ + pub fn new(calc: T) -> Cacher { + Cacher { calc, values: HashMap::new() } + } + + pub fn value(&mut self, arg: A) -> V { + match self.values.get(&arg) { + Some(v) => *v, + None => { + let v = (self.calc)(arg); + self.values.insert(arg, v); + v + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 718ecb5..0ef1b16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,10 +137,31 @@ fn test_generics() { println!("longest string: {}", l); } +fn test_closures() { + let mut expensive_int_result = Cacher::new(|x| { + println!("calculating expensive int result for {}", x); + 2 * x + }); + + println!("1st int call for value 1: {}", expensive_int_result.value(1)); + println!("2nd int call for value 1: {}", expensive_int_result.value(1)); + println!("1st int call for value 2: {}", expensive_int_result.value(2)); + + let mut expensive_str_result = Cacher::new(|x: &str| { + println!("calculating expensive str result for {}", x); + x.len() + }); + + println!("1st int call for value abc: {}", expensive_str_result.value("abc")); + println!("2nd int call for value abc: {}", expensive_str_result.value("abc")); + println!("1st int call for value defg: {}", expensive_str_result.value("defg")); +} + fn main() { test_strings(); test_vectors(); test_hashmaps(); test_files(); test_generics(); + test_closures(); } diff --git a/tests/test_lib.rs b/tests/test_lib.rs index 44d7f87..186ffd7 100644 --- a/tests/test_lib.rs +++ b/tests/test_lib.rs @@ -6,3 +6,46 @@ fn test_longest() { assert_eq!(longest("abc", "def"), "def"); assert_eq!(longest("abc", "defg"), "defg"); } + +// FIXME: How to make this not unsafe? +static mut CALLED: u32 = 0; + +#[test] +fn test_cacher_int_int() { + unsafe { CALLED = 0; } + let mut cacher = Cacher::new(|x| { + unsafe { CALLED += 1; } + 2 * x + }); + assert_eq!(cacher.value(1), 2); + unsafe { assert_eq!(CALLED, 1); } + // second time cached + assert_eq!(cacher.value(1), 2); + unsafe { assert_eq!(CALLED, 1); } + // re-evaluated for new value + assert_eq!(cacher.value(-2), -4); + unsafe { assert_eq!(CALLED, 2); } + // old arg still cached + assert_eq!(cacher.value(1), 2); + unsafe { assert_eq!(CALLED, 2); } +} + +#[test] +fn test_cacher_str_usize() { + unsafe { CALLED = 0; } + let mut cacher = Cacher::new(|x: &str| { + unsafe { CALLED += 1; } + x.len() + }); + assert_eq!(cacher.value("abc"), 3); + unsafe { assert_eq!(CALLED, 1); } + // second time cached + assert_eq!(cacher.value("abc"), 3); + unsafe { assert_eq!(CALLED, 1); } + // re-evaluated for new value + assert_eq!(cacher.value("defg"), 4); + unsafe { assert_eq!(CALLED, 2); } + // old arg still cached + assert_eq!(cacher.value("abc"), 3); + unsafe { assert_eq!(CALLED, 2); } +} -- 2.39.2