是否可以对HashMap的键和值使用单个泛型? [英] Is it possible to use a single generic for both key and value of a HashMap?

查看:59
本文介绍了是否可以对HashMap的键和值使用单个泛型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Rust书的第13章中,您实现一个 Cacher 以使用备注来演示功能性编程以及如何加快长时间运行的任务.作为额外的挑战,他们建议使用 HashMap 使 Cacher 允许多个键,并利用泛型来提供更大的灵活性.

尝试修改 Cacher 以保存哈希图,而不是单个值.哈希映射的键将是传入的 arg 值,哈希图的值将是调用在该键上关闭.而不是直接查看 self.value 具有 Some None 值,值函数将在其中查找 arg 哈希图,并返回值(如果存在).如果不是目前, Cacher 将调用闭包并保存结果值在与其 arg 值关联的哈希图中.

当前 Cacher 实现的第二个问题是仅接受采用一个 u32 类型的参数并返回a的闭包 u32 .我们可能要缓存采用字符串的闭包结果例如,切片并返回 usize 值.要解决此问题,请尝试引入更多通用参数以增加 Cacher 功能.

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

 使用std :: collections :: hash_map :: Entry;使用std :: collections :: HashMap;使用std :: thread;使用std :: time :: Duration;结构Cacher< a,T>在哪里T:Fn(&'a u32)->&'u32,{计算:T,值:HashMap<&'a u32,&'a u32> ;、}impl’a,T>Cacher’a,T>在哪里T:Fn(&'a u32)->&'u32,{fn new(计算:T)->Cacher’a,T>{Cacher {计算,值:HashMap :: new(),}}fn值(& mut self,arg:&'a u32)->& a u32 {匹配self.values.entry(arg){Entry :: Occupied(e)=>& * e.into_mut(),条目::空缺(e)=>& * e.insert(&(自我计算)(& arg)),}}}fn generate_workout(intensity:u32,random_number:u32){let mut cheap_result = Cacher :: new(| num | {println!(计算缓慢...");thread :: sleep(Duration :: from_secs(2));#});如果强度<25 {println!(今天,做{}俯卧撑!",cheap_result.values(& intensity));println!(下一步,做{}仰卧起坐!",cheap_result.values(& intensity));} 别的 {如果random_number == 3 {println!(今天休息一下!记得保持水分!");} 别的 {println!(今天,运行{}分钟!",cheap_result.values(& intensity));}}}fn main(){让simulated_user_specified_value = 10;让simulated_random_number = 7;generate_workout(simulated_user_specified_value,simulated_random_number);} 

我尝试了如下的 K,V 泛型,并且它抱怨在此期望7个可能的值之一指向第一个类型定义.

 使用std :: collections :: hash_map :: Entry;使用std :: collections :: HashMap;使用std :: hash :: Hash;使用std :: thread;使用std :: time :: Duration;struct Cacher<'a,T:'a,K:'a,V:'a>在哪里T:Fn(& a K)->& a V,K:哈希+等式,{计算:T,值:HashMap<" a K,&'a V> ;,}impl<'a,T:'a,K:'a,V:'a>Cacher< a,T:'a,K:'a,V:'a>在哪里T:Fn(& a K)->& a V,K:哈希+等式,{fn new(计算:T)->Cacher< a,T:'a,K:'a,V:'a>{Cacher {计算,值:HashMap :: new(),}}fn值(& mut self,arg:&'a K)->& a V {匹配self.values.entry(arg){Entry :: Occupied(e)=>& * e.into_mut(),条目::空缺(e)=>& * e.insert(&(自我计算)(& arg)),}}}fn generate_workout(intensity:u32,random_number:u32){let mut cheap_result = Cacher :: new(| num | {println!(计算缓慢...");thread :: sleep(Duration :: from_secs(2));#});如果强度<25 {println!(今天,做{}俯卧撑!",cheap_result.values(& intensity));println!(下一步,做{}仰卧起坐!",cheap_result.values(& intensity));} 别的 {如果random_number == 3 {println!(今天休息一下!记得保持水分!");} 别的 {println!(今天,运行{}分钟!",cheap_result.values(& intensity));}}}fn main(){让simulated_user_specified_value = 10;让simulated_random_number = 7;generate_workout(simulated_user_specified_value,simulated_random_number);} 

导致以下错误:

 错误:预期为`!`,`(`,`+ 、、、、 ::,`<`或`>`,找到`:`->src/main.rs:16:39|16 |impl<'a,T:'a,K:'a,V:'a>Cacher< T:'a,K:'a,V:'a>|^预期是这里7种可能的令牌之一 

是添加两个以上泛型(即 K V )的唯一方法,还是有一种方法可以重用单个泛型?如果需要2,那么我在上面遗漏了什么?

是否有更惯用的方法来解决此问题?不幸的是,Rust书没有提供解决方案.

解决方案

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

  impl<'a,T:'a,K:'a,V:'a>Cacher’a,T,K,V’为1.在哪里T:Fn(& a K)->& a V,K:哈希+等式,{fn new(计算:T)->Cacher’a,T,K,V’为1.{Cacher {计算,值:HashMap :: new(),}}} 

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

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

  struct Cacher< T,K,V>在哪里T:Fn(K)→V,{计算:T,值:HashMap< K,V> ;,}impl< T,K,V>Cacher T,K,V = 1.在哪里T:Fn(K)→V,K:哈希+均衡+克隆{fn new(计算:T)->Cacher T,K,V = 1.{Cacher {计算,值:HashMap :: new(),}}fn值(& mut self,arg:K)->& V {匹配self.value.entry(arg.clone()){Entry :: Occupied(v)=>v.into_mut(),Entry :: Vacant(v)=>v.insert((self.calculation)(arg)),}}} 

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

的约束.

In chapter 13 of the Rust book, you implement a Cacher to use memoization to demonstrate functional programming and how to speed up long-running tasks. As an extra challenge, they recommend making the Cacher allow multiple keys using a HashMap and also leveraging generics to allow more flexibility.

Try modifying Cacher to hold a hash map rather than a single value. The keys of the hash map will be the arg values that are passed in, and the values of the hash map will be the result of calling the closure on that key. Instead of looking at whether self.value directly has a Some or a None value, the value function will look up the arg in the hash map and return the value if it’s present. If it’s not present, the Cacher will call the closure and save the resulting value in the hash map associated with its arg value.

The second problem with the current Cacher implementation is that it only accepts closures that take one parameter of type u32 and return a u32. We might want to cache the results of closures that take a string slice and return usize values, for example. To fix this issue, try introducing more generic parameters to increase the flexibility of the Cacher functionality.

I was able to implement the HashMap, however when trying to replace the closure definition u32 with a generic type and use that as the signature of the HashMap, I run into an issue.

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);
}

I tried K, V generics as below and it complains with Expected one of 7 possible values here pointing to the first type definition.

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);
}

Results in the following error:

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

Is the only way to add 2 more generics (i.e. K, V) or is there a way to reuse a single generic? If 2 required, what am I missing above?

Is there a more idiomatic approach to solving this problem? The Rust book does not offer a solution, unfortunately.

解决方案

Your implementation does not compile because lifetime bounds have to be declared only after 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(),
        }
    }
}

Storing references into the HashMap implies that you have to manage lifetimes and assure that the values referenced by HashMap outlive the Cacher.

Another approach to consider may be to cache by values:

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)),
        }
    }
}

Please note that in this solution I added the constraint that K is Clone

这篇关于是否可以对HashMap的键和值使用单个泛型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆