【Rust】使用HashMap解决官方文档中的闭包限制([rust] use HashMap to solve closure restrictions in official documents)

问题概述

值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 Cacher 的实现存在两个小问题,这使得在不同上下文中复用变得很困难。
第一个问题是 Cacher 实例假设对于 value 方法的任何 arg 参数值总是会返回相同的值。也就是说,这个 Cacher 的测试会失败:
《rust程序设计语言》13章闭包内容提出的问题

值缓存是一种更加广泛的实用行为,我们可能希望在代码中的其他闭包中也使用他们。然而,目前 Cacher 的实现存在两个小问题,这使得在不同上下文中复用变得很困难。

第一个问题是 Cacher 实例假设对于 value 方法的任何 arg 参数值总是会返回相同的值。也就是说,这个 Cacher 的测试会失败:

 #[test]
    fn call_with_different_values() {
        let mut c = Cacher::new(|a| a);

        let v1 = c.value(1);
        let v2 = c.value(2);

        assert_eq!(v2, 2);
    }

具体代码请在《rust程序设计语言》第13章第一节  实现的限制 中找到。

Cacher

解决思路

尝试修改 Cacher 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 arg 值,而 value 则是对应 key 调用闭包的结果值。
相比之前检查 self.value 直接是 Some 还是 None 值,现在 value 函数会在哈希 map 中寻找 arg,如果找到的话就返回其对应的值。如果不存在,Cacher 会调用闭包并将结果值保存在哈希 map 对应 arg 值的位置。
《rust程序设计语言》13章闭包内容提出的解决思路

尝试修改 Cacher 存放一个哈希 map 而不是单独一个值。哈希 map 的 key 将是传递进来的 arg 值,而 value 则是对应 key 调用闭包的结果值。

相比之前检查 self.value 直接是 Some 还是 None 值,现在 value 函数会在哈希 map 中寻找 arg,如果找到的话就返回其对应的值。如果不存在,Cacher 会调用闭包并将结果值保存在哈希 map 对应 arg 值的位置。

解决方案

更改Cacher结构体的类型

use std::collections::HashMap;
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: HashMap<u32, u32>,
}

将之前储存单一u32类型的 value字段 替换成 HashMap类型,此HashMap的key和value都为u32类型

更改chacher结构体的方法

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value.get(&arg) {
            Some(v) => *v,
            None => {
                let v = (self.calculation)(arg);
                self.value.insert(arg, v);
                arg
            }
        }
    }
}

new方法中返回的Cacher实例,value字段 不再为Option<u32>类型,取而代之的是一个 被初始化的HashMap,用于存放 不同参数的结果缓存

value方法中,不再直接匹配结构体的value字段,而是通过 参数 去value字段的 HashMap 中找到储存的值并返回,要是找不到则在HashMap中插入 key值为传入参数value值为结构体闭包调用参数所得的结果。

测试结果

fn main() {
    let mut cal = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    println!("{}", cal.value(1));
    println!("{}", cal.value(2));
    println!("{}", cal.value(1))
}
cargo run
   Compiling closure v0.1.0 (D:\project\rust\closure)
    Finished dev [unoptimized + debuginfo] target(s) in 0.66s
     Running `target\debug\closure.exe`
calculating slowly...
1
calculating slowly...
2
1

测试通过

————————

Problem overview

Value caching is a more widely used behavior, and we may want to use them in other closures in our code. However, there are two small problems in the current implementation of cache, which makes it difficult to reuse in different contexts.
The first problem is that the cache instance assumes that any Arg parameter value of the value method will always return the same value. In other words, the cache test will fail:
Problems raised in Chapter 13 closure of trust programming language

Value caching is a more widely used behavior, and we may want to use them in other closures in our code. However, there are two small problems in the current implementation of cache, which makes it difficult to reuse in different contexts.

The first problem is that the cache instance assumes that any Arg parameter value of the value method will always return the same value. In other words, the cache test will fail:

 #[test]
    fn call_with_different_values() {
        let mut c = Cacher::new(|a| a);

        let v1 = c.value(1);
        let v2 = c.value(2);

        assert_eq!(v2, 2);
    }

The specific code can be found in < / strong > implementation restrictions in Section 1 of Chapter 13 of trust programming language.

Cacher

Solution ideas

Try to modify the cache to store a hash map instead of a single value. The key of the hash map will be the Arg value passed in, and value is the result value of the closure of the corresponding key call.
Compared with the previous inspection, {self Value , is directly the , some , or , none , value. Now the , value , function will look for , Arg in the hash map and return its corresponding value if found. If it does not exist, the Cacher , will call the closure and save the result value in the location of the , Arg , value corresponding to the hash map.
Solutions to closures in Chapter 13 of trust programming language

Try to modify the cache to store a hash map instead of a single value. The key of the hash map will be the Arg value passed in, and value is the result value of the closure of the corresponding key call.

Compared with the previous inspection, {self Value , is directly the , some , or , none , value. Now the , value , function will look for , Arg in the hash map and return its corresponding value if found. If it does not exist, the Cacher , will call the closure and save the result value in the location of the , Arg , value corresponding to the hash map.

Solution

Change the type of the cache structure

use std::collections::HashMap;
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: HashMap<u32, u32>,
}

Replace the previously stored < strong > value field of a single U32 type with < strong > HashMap type < / strong >, and the key and value of this HashMap are < strong > U32 type < / strong >.

How to change the chacher structure

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value.get(&arg) {
            Some(v) => *v,
            None => {
                let v = (self.calculation)(arg);
                self.value.insert(arg, v);
                arg
            }
        }
    }
}

For the cache instance returned in the new method, the < strong > value field < / strong > is no longer option & lt; u32> Type, instead of a < strong > initialized HashMap < / strong >, which is used to store the result cache of < strong > different parameters < / strong >.

In the value method, instead of directly matching the value field of the structure, find the stored value in the < strong > HashMap < / strong > of the value field through the < strong > parameter < / strong > and return it. If it cannot be found, insert the < strong > key value in the HashMap as the incoming parameter < / strong >, and the < strong > value is the result of the structure closure calling the parameter < / strong >.

test result

fn main() {
    let mut cal = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    println!("{}", cal.value(1));
    println!("{}", cal.value(2));
    println!("{}", cal.value(1))
}
cargo run
   Compiling closure v0.1.0 (D:\project\rust\closure)
    Finished dev [unoptimized + debuginfo] target(s) in 0.66s
     Running `target\debug\closure.exe`
calculating slowly...
1
calculating slowly...
2
1

< strong > test passed < / strong >