是否可以对HashMap的键和值使用单个泛型?

时间:2018-04-27 05:27:04

标签: generics rust closures

chapter 13 of the Rust book中,您实现Cacher以使用memoization来演示函数式编程以及如何加速长时间运行的任务。作为额外的挑战,他们建议Cacher使用HashMap允许多个密钥,并利用泛型来提供更大的灵活性。

  

尝试修改Cacher以保存哈希映射而不是单个值。   哈希映射的键将是传入的arg值,   并且哈希映射的值将是调用的结果   关闭那把钥匙。而不是直接查看是否self.value   如果值为SomeNone,则值函数将在arg中查找   哈希映射并返回值(如果存在)。如果不是   现在,Cacher将调用闭包并保存结果值   在与其arg值关联的哈希映射中。

     

当前Cacher实施的第二个问题是它   只接受带有u32类型参数的闭包并返回a   u32。我们可能想要缓存带字符串的闭包的结果   例如,切片并返回usize值。要解决此问题,请尝试   引入更多通用参数以增加灵活性   Cacher功能。

我能够实现HashMap,但是当尝试用通用类型替换闭包定义u32并将其用作HashMap的签名时,我遇到了问题。

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::thread;
use std::time::Duration;

struct Cacher<'a, T>
where
    T: Fn(&'a u32) -> &'a u32,
{
    calculation: T,
    values: HashMap<&'a u32, &'a u32>,
}

impl<'a, T> Cacher<'a, T>
where
    T: Fn(&'a u32) -> &'a u32,
{
    fn new(calculation: T) -> Cacher<'a, T> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn values(&mut self, arg: &'a u32) -> &'a u32 {
        match self.values.entry(arg) {
            Entry::Occupied(e) => &*e.into_mut(),
            Entry::Vacant(e) => &*e.insert(&(self.calculation)(&arg)),
        }
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        &num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.values(&intensity));
        println!("Next, do {} situps!", expensive_result.values(&intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.values(&intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

我尝试了K, V泛型,如下所示,它抱怨Expected one of 7 possible values here指向第一个类型定义。

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::Hash;
use std::thread;
use std::time::Duration;

struct Cacher<'a, T: 'a, K: 'a, V: 'a>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    calculation: T,
    values: HashMap<&'a K, &'a V>,
}

impl<'a, T: 'a, K: 'a, V: 'a> Cacher<'a, T: 'a, K: 'a, V: 'a>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    fn new(calculation: T) -> Cacher<'a, T: 'a, K: 'a, V: 'a> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn values(&mut self, arg: &'a K) -> &'a V {
        match self.values.entry(arg) {
            Entry::Occupied(e) => &*e.into_mut(),
            Entry::Vacant(e) => &*e.insert(&(self.calculation)(&arg)),
        }
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        &num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.values(&intensity));
        println!("Next, do {} situps!", expensive_result.values(&intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.values(&intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

导致以下错误:

error: expected one of `!`, `(`, `+`, `,`, `::`, `<`, or `>`, found `:`
  --> src/main.rs:16:39
   |
16 | impl<'a, T: 'a, K: 'a, V: 'a> Cacher<T: 'a, K: 'a, V: 'a>
   |                                       ^ expected one of 7 possible tokens here

是添加2个更多泛型的唯一方法(例如KV)还是有办法重用单个泛型?如果需要2,我上面缺少什么?

有没有更惯用的方法来解决这个问题?不幸的是,Rust书并没有提供解决方案。

2 个答案:

答案 0 :(得分:1)

您的实现无法编译,因为必须在impl之后声明生命周期边界:

impl<'a, T: 'a, K: 'a, V: 'a> Cacher<'a, T, K, V>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    fn new(calculation: T) -> Cacher<'a, T, K, V> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }
}

将引用存储到HashMap意味着您必须管理生命周期并确保HashMap引用的值比Cacher更长。

另一种需要考虑的方法可能是按值缓存:

struct Cacher<T, K, V>
where
    T: Fn(K) -> V,
{
    calculation: T,
    value: HashMap<K, V>,
}

impl<T, K, V> Cacher<T, K, V>
where
    T: Fn(K) -> V,
    K: Hash + Eq + Clone
{
    fn new(calculation: T) -> Cacher<T, K, V> {
        Cacher {
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(& mut self, arg: K) -> &V {    
        match self.value.entry(arg.clone()) {
            Entry::Occupied(v) => v.into_mut(),
            Entry::Vacant(v) =>   v.insert((self.calculation)(arg)),
        }
    }
}

请注意,在此解决方案中,我添加了KClone

的约束

答案 1 :(得分:0)

这个问题很老了,但我在网上浏览时发现了这个问题,看看我的解决方案是否“正确”。我将在这里分享它,因为它与接受的答案略有不同,并且保留了书中 value 方法的原始签名:

struct Cacher<T, K, V>
    where T: Fn(K) -> V,
          K: Eq + Hash + Copy,
          V: Copy {
    calculation: T,
    cache: HashMap<K, V>,
}

impl<T, K, V> Cacher<T, K, V>
    where T: Fn(K) -> V,
          K: Eq + Hash + Copy,
          V: Copy {
    fn new(calculation: T) -> Cacher<T, K, V> {
        Cacher {
            calculation,
            cache: HashMap::new(),
        }
    }

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

在这里,我为 HashMap 的键和值设置了明确的通用参数,这些参数将缓存 Cacher 的值。 KEqHashCopy 的 trait bound,而 V 只需要 Copy trait bound。