这是对niveaus最高的抱怨,但我解决了沙沙声课程中的一项任务,我相信这不是最佳解决方案 - 甚至不是一个好的解决方案。
任务:https://github.com/rust-lang/rustlings/blob/main/exercises/hashmaps/hashmaps3.rs
我的解决方案(仅相关部分):
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
name: team_1_name.clone(),
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
let team_2 = scores.entry(team_2_name.clone()).or_insert(Team {
name: team_2_name.clone(),
goals_scored: 0,
goals_conceded: 0,
});
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
scores
}
我的问题是,我在
.entry()
方法以及 Team 结构中克隆字符串(两次!)。我尝试不使用它,但它不起作用(借用东西)并使用 &
但它不高兴,因为它期望 String
- 而不是 &String
。
不确定到底是什么不起作用?您可以移动到第二个使用站点,效果很好:
let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
name: team_1_name,
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
如果您希望在成功案例中实现零克隆(团队已经有一个条目),则代码有点不那么性感,但它也可以很好地编译:
if let Some(t) = scores.get_mut(&team_1_name) {
t.goals_scored += team_1_score;
t.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name.clone(), Team {
name: team_1_name,
goals_scored: team_1_score,
goals_conceded: team_2_score,
});
}
从技术上讲,我们甚至可以删除初始的
to_string
并且在命中情况下不进行分配(显然这意味着在未命中情况下进行两次分配):
let team_1_name = v[0];
let team_1_score: u8 = v[2].parse().unwrap();
// ...
if let Some(t) = scores.get_mut(team_1_name) {
t.goals_scored += team_1_score;
t.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name.to_string(), Team {
name: team_1_name.to_string(),
goals_scored: team_1_score,
goals_conceded: team_2_score,
});
}
或者,从
name
结构中删除 Team
,它并不是真正有价值,因为你有 hashmap 键。虽然此时您不再有 Team
结构,所以它可能应该重命名,例如Score
或 Goals
(您可以从左边的两个成员中删除前缀)。
您不需要第二个
.clone()
调用(在 Team
中),因为您可以允许结构取得该调用的所有权,因为之后没有其他东西使用它。他们设置的哈希图对你来说有点奇怪,因为键和值都包含团队名称。如果您只是将其从结构定义中删除,则不再需要克隆(无论如何,它从未被使用过)。
struct Team {
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = scores.entry(team_1_name).or_insert(Team {
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
let team_2 = scores.entry(team_2_name).or_insert(Team {
goals_scored: 0,
goals_conceded: 0,
});
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
scores
}
这就是我最终得到的结果,并不是因为必须使用“and_modify”两次而疯狂,但这就是生活。
fn build_scores_table(results: String) -> HashMap<String, Team> {
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
scores.entry(team_1_name.clone())
.and_modify(|a| a.goals_scored += team_1_score)
.and_modify(|b| b.goals_conceded += team_2_score)
.or_insert(Team { name: team_1_name,
goals_scored: team_1_score,
goals_conceded: team_2_score });
scores.entry(team_2_name.clone())
.and_modify(|a| a.goals_scored += team_2_score)
.and_modify(|b| b.goals_conceded += team_1_score)
.or_insert(Team { name: team_2_name,
goals_scored: team_2_score,
goals_conceded: team_1_score });
}
scores
}
学到东西了!谢谢!
顺便说一句,我有一个后续问题:与 Rust 书 相比,为什么我们不能在这一行添加 (*)
team_1.goals_scored += team_1_score;
成为
*team_1.goals_scored += team_1_score;
失败有什么原因吗?
我认为这与作为hashmap值的是类型
Team
而不是原始类型这一事实有些关系,但我仍然希望对此有一个清晰的理解。
关于后续问题,您是否尝试过以下操作:
(*team_1).goals_scored += team_1_score;
天哪,堆栈上的第一个响应,但我有这个。 (参考为什么你不需要取消引用更新)
TLDR 是,在 rust 中,复合类型(vecs、structs、enums...)使用 Deref 特征,当你正在做一些移动到字段、是索引或是的事情时,它会在幕后为你解引用对复合类型引用的方法调用。
更长的形式:
team_1
是一个变量。您本质上是绑定一个方法(特别是 .insert),该方法返回一个可变引用(&mut 值),然后将其保存到该变量。
这种情况下的值不仅仅是像整数这样的基本类型,而是复合类型(结构体)。 稍后,您将通过作为对该结构的可变引用的变量来调用该结构中的字段。这时,结构体的 Deref 特征就会发挥作用,并会自动为您提供
*team_1
。
证据就是当您尝试输入 *team_1.goals_scored
而没有 ()
时。 Rust 会查看已经被解引用为 u8 类型的 team_1.goals_scored
,并发现你在问 *u8
这不是一个东西。 (*team_1).goals_scored
之所以有效,是因为您明确地执行了*&mut value
,但是,由于此可变引用背后的事物具有 Deref 特征,因此这是不必要的。
也许会出现另一个问题,比如“如果他们已经有了可以实现到类型上的 Deref 特征,为什么不对原始类型也这样做呢?” 有几个原因......但老实说,我唯一能理解的是,使用过 rust 的人习惯于需要显式取消引用原始类型,因此改变这一点会惹恼社区并破坏当前的使用如果更新的话.... 另一个问题是将这种抽象添加到操作原始类型的每个实例中的潜在成本,我认为这可能会增加分配? 最后一个是“它可能会增加未知的复杂性”......我还没有想到什么时候会发生这种情况,除了从一种事物方式转变为一种新的事物方式......