是否可以对HashMap的键和值使用单个泛型? [英] Is it possible to use a single generic for both key and value of a 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 thearg
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 whetherself.value
directly has aSome
or aNone
value, the value function will look up thearg
in the hash map and return the value if it’s present. If it’s not present, theCacher
will call the closure and save the resulting value in the hash map associated with itsarg
value.The second problem with the current
Cacher
implementation is that it only accepts closures that take one parameter of typeu32
and return au32
. We might want to cache the results of closures that take a string slice and returnusize
values, for example. To fix this issue, try introducing more generic parameters to increase the flexibility of theCacher
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屋!