Rust 中的 HashMap 实战指南:理解与优化技巧

打印 上一主题 下一主题

主题 914|帖子 914|积分 2742

Rust 中的 HashMap 实战指南:理解与优化技巧

在 Rust 编程中,HashMap 是一个强大的键值对数据布局,广泛应用于数据统计、信息存储等场景。在本文中,我们将通过三个实际的代码示例,详细讲解 HashMap 的根本用法以及如安在真实项目中充分利用它。别的,我们还将探讨 Rust 的所有权体系对 HashMap 的影响,并分享避免常见陷阱的技巧。
本文通过三个 Rust 实战例子,展示了 HashMap 的根本用法及其在实际场景中的应用。我们将从简单的水果篮子示例出发,逐步演示如何利用 HashMap 存储和处置惩罚不同数据,并通过添加测试用例来确保代码的正确性。别的,我们还会深入探讨 Rust 所有权体系对 HashMap 利用的影响,尤其是如何避免所有权转移的问题。
实操

示例一:利用 HashMap 存储水果篮子
  1. // hashmaps1.rs
  2. //
  3. // A basket of fruits in the form of a hash map needs to be defined. The key
  4. // represents the name of the fruit and the value represents how many of that
  5. // particular fruit is in the basket. You have to put at least three different
  6. // types of fruits (e.g apple, banana, mango) in the basket and the total count
  7. // of all the fruits should be at least five.
  8. //
  9. // Make me compile and pass the tests!
  10. //
  11. // Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a
  12. // hint.
  13. use std::collections::HashMap;
  14. fn fruit_basket() -> HashMap<String, u32> {
  15.     let mut basket = HashMap::new(); // TODO: declare your hash map here.
  16.     // Two bananas are already given for you :)
  17.     basket.insert(String::from("banana"), 2);
  18.     // TODO: Put more fruits in your basket here.
  19.     basket.insert(String::from("apple"), 3);
  20.     basket.insert(String::from("mango"), 4);
  21.     basket.insert(String::from("orange"), 5);
  22.     basket
  23. }
  24. #[cfg(test)]
  25. mod tests {
  26.     use super::*;
  27.     #[test]
  28.     fn at_least_three_types_of_fruits() {
  29.         let basket = fruit_basket();
  30.         assert!(basket.len() >= 3);
  31.     }
  32.     #[test]
  33.     fn at_least_five_fruits() {
  34.         let basket = fruit_basket();
  35.         assert!(basket.values().sum::<u32>() >= 5);
  36.     }
  37. }
复制代码
在 本例中,我们构建了一个水果篮子,并通过 HashMap 来存储水果种类及其数量。
通过测试,我们验证了篮子中至少有三种水果,而且总数超过五个。
示例二:不重复添加水果
  1. // hashmaps2.rs
  2. //
  3. // We're collecting different fruits to bake a delicious fruit cake. For this,
  4. // we have a basket, which we'll represent in the form of a hash map. The key
  5. // represents the name of each fruit we collect and the value represents how
  6. // many of that particular fruit we have collected. Three types of fruits -
  7. // Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You
  8. // must add fruit to the basket so that there is at least one of each kind and
  9. // more than 11 in total - we have a lot of mouths to feed. You are not allowed
  10. // to insert any more of these fruits!
  11. //
  12. // Make me pass the tests!
  13. //
  14. // Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a
  15. // hint.
  16. use std::collections::HashMap;
  17. #[derive(Hash, PartialEq, Eq)]
  18. enum Fruit {
  19.     Apple,
  20.     Banana,
  21.     Mango,
  22.     Lychee,
  23.     Pineapple,
  24. }
  25. fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
  26.     let fruit_kinds = vec![
  27.         Fruit::Apple,
  28.         Fruit::Banana,
  29.         Fruit::Mango,
  30.         Fruit::Lychee,
  31.         Fruit::Pineapple,
  32.     ];
  33.     for fruit in fruit_kinds {
  34.         // TODO: Insert new fruits if they are not already present in the
  35.         // basket. Note that you are not allowed to put any type of fruit that's
  36.         // already present!
  37.         *basket.entry(fruit).or_insert(1);
  38.         // 如果水果不在篮子中,则插入数量为1的该水果
  39.         // if !basket.contains_key(&fruit) {
  40.         //     basket.insert(fruit, 1);
  41.         // }
  42.     }
  43. }
  44. #[cfg(test)]
  45. mod tests {
  46.     use super::*;
  47.     // Don't modify this function!
  48.     fn get_fruit_basket() -> HashMap<Fruit, u32> {
  49.         let mut basket = HashMap::<Fruit, u32>::new();
  50.         basket.insert(Fruit::Apple, 4);
  51.         basket.insert(Fruit::Mango, 2);
  52.         basket.insert(Fruit::Lychee, 5);
  53.         basket
  54.     }
  55.     #[test]
  56.     fn test_given_fruits_are_not_modified() {
  57.         let mut basket = get_fruit_basket();
  58.         fruit_basket(&mut basket);
  59.         assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
  60.         assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
  61.         assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
  62.     }
  63.     #[test]
  64.     fn at_least_five_types_of_fruits() {
  65.         let mut basket = get_fruit_basket();
  66.         fruit_basket(&mut basket);
  67.         let count_fruit_kinds = basket.len();
  68.         assert!(count_fruit_kinds >= 5);
  69.     }
  70.     #[test]
  71.     fn greater_than_eleven_fruits() {
  72.         let mut basket = get_fruit_basket();
  73.         fruit_basket(&mut basket);
  74.         let count = basket.values().sum::<u32>();
  75.         assert!(count > 11);
  76.     }
  77.     #[test]
  78.     fn all_fruit_types_in_basket() {
  79.         let mut basket = get_fruit_basket();
  80.         fruit_basket(&mut basket);
  81.         for amount in basket.values() {
  82.             assert_ne!(amount, &0);
  83.         }
  84.     }
  85. }
复制代码
在上面的示例代码中,我们通过 HashMap 存储多个水果,但避免重复添加已有的水果种类。
测试用例验证了我们不会修改已存在的水果,并确保总数超过 11 个。
示例三:记录角逐比分
  1. // hashmaps3.rs
  2. //
  3. // A list of scores (one per line) of a soccer match is given. Each line is of
  4. // the form : "<team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>"
  5. // Example: England,France,4,2 (England scored 4 goals, France 2).
  6. //
  7. // You have to build a scores table containing the name of the team, goals the
  8. // team scored, and goals the team conceded. One approach to build the scores
  9. // table is to use a Hashmap. The solution is partially written to use a
  10. // Hashmap, complete it to pass the test.
  11. //
  12. // Make me pass the tests!
  13. //
  14. // Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a
  15. // hint.
  16. use std::collections::HashMap;
  17. // A structure to store the goal details of a team.
  18. struct Team {
  19.     goals_scored: u8,
  20.     goals_conceded: u8,
  21. }
  22. fn build_scores_table(results: String) -> HashMap<String, Team> {
  23.     // The name of the team is the key and its associated struct is the value.
  24.     let mut scores: HashMap<String, Team> = HashMap::new();
  25.     for r in results.lines() {
  26.         let v: Vec<&str> = r.split(',').collect();
  27.         let team_1_name = v[0].to_string();
  28.         let team_1_score: u8 = v[2].parse().unwrap();
  29.         let team_2_name = v[1].to_string();
  30.         let team_2_score: u8 = v[3].parse().unwrap();
  31.         // TODO: Populate the scores table with details extracted from the
  32.         // current line. Keep in mind that goals scored by team_1
  33.         // will be the number of goals conceded from team_2, and similarly
  34.         // goals scored by team_2 will be the number of goals conceded by
  35.         // team_1.
  36.         // 更新 team_1 的数据
  37.         let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
  38.             goals_scored: 0,
  39.             goals_conceded: 0,
  40.         });
  41.         team_1.goals_scored += team_1_score;
  42.         team_1.goals_conceded += team_2_score;
  43.         // 更新 team_2 的数据
  44.         let team_2 = scores.entry(team_2_name.clone()).or_insert(Team {
  45.             goals_scored: 0,
  46.             goals_conceded: 0,
  47.         });
  48.         team_2.goals_scored += team_2_score;
  49.         team_2.goals_conceded += team_1_score;
  50.     }
  51.     scores
  52. }
  53. #[cfg(test)]
  54. mod tests {
  55.     use super::*;
  56.     fn get_results() -> String {
  57.         let results = "".to_string()
  58.             + "England,France,4,2\n"
  59.             + "France,Italy,3,1\n"
  60.             + "Poland,Spain,2,0\n"
  61.             + "Germany,England,2,1\n";
  62.         results
  63.     }
  64.     #[test]
  65.     fn build_scores() {
  66.         let scores = build_scores_table(get_results());
  67.         let mut keys: Vec<&String> = scores.keys().collect();
  68.         keys.sort();
  69.         assert_eq!(
  70.             keys,
  71.             vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
  72.         );
  73.     }
  74.     #[test]
  75.     fn validate_team_score_1() {
  76.         let scores = build_scores_table(get_results());
  77.         let team = scores.get("England").unwrap();
  78.         assert_eq!(team.goals_scored, 5);
  79.         assert_eq!(team.goals_conceded, 4);
  80.     }
  81.     #[test]
  82.     fn validate_team_score_2() {
  83.         let scores = build_scores_table(get_results());
  84.         let team = scores.get("Spain").unwrap();
  85.         assert_eq!(team.goals_scored, 0);
  86.         assert_eq!(team.goals_conceded, 2);
  87.     }
  88. }
复制代码
本示例展示了 HashMap 在复杂场景中的应用,如记录足球角逐的比分。我们通过 HashMap 将每支球队的得分和失分举行统计。并通过测试来验证比分记录是否正确。
思考

1. 为什么要用 team_1_name.clone()?

在 Rust 中,String 是一个拥有所有权的类型,意味着它的值在默认情况下会被移动,而不是复制。如果你直接利用 team_1_name 作为 HashMap 的键,那么当你调用 entry(team_1_name) 时,team_1_name 的所有权会被移动到 entry() 函数中。
之后,如果你还想利用 team_1_name,就无法访问它了,因为所有权已经被移动了。这时你需要通过 clone() 创建一个新的副本(浅拷贝),如许你可以保留原始的 String。
利用 .clone() 的目标是避免所有权转移而导致变量不可用。
示例:
  1. let team_1_name = "England".to_string();
  2. // 所有权被移动给 entry(),你不能再访问 team_1_name
  3. scores.entry(team_1_name);
  4. // 如果你还想用 team_1_name,就要使用 clone():
  5. scores.entry(team_1_name.clone());
复制代码
如果 team_1_name 是一个 &str(即字符串切片,通常是不可变引用),那么你就不需要 clone(),因为引用类型不涉及所有权的移动问题。
2. 为什么不消布局体直接初始化,而是用累加的方式?

在每场角逐的过程中,某个队伍可能会多次出现,比方:

  • 角逐1:England 对 France
  • 角逐2:Germany 对 England
我们需要在 HashMap 中更新每个队伍的进球和失球信息,而不是每次都覆盖已有数据。因此,我们不能每次都用新的布局体初始化,而是要先检查该队伍是否已经在 HashMap 中存在,然后累加其数据。
这里用的是 entry() 方法,它的作用是:

  • 如果 team_1_name 还没有在 HashMap 中出现,就插入一个新的 Team 布局体,并初始化进球和失球为 0。
  • 如果 team_1_name 已经在 HashMap 中了,那么直接获取它对应的 Team 布局体,并更新其 goals_scored 和 goals_conceded 字段。
通过这种方式,每次碰到相同队伍时,不会重新初始化,而是将新的进球和失球数累加到已有数据中。
  1. let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
  2.     goals_scored: 0,
  3.     goals_conceded: 0,
  4. });
  5. // 累加进球和失球
  6. team_1.goals_scored += team_1_score;
  7. team_1.goals_conceded += team_2_score;
复制代码
如许就能保证每个队伍的分数在不同角逐中是累积的,而不是被覆盖掉。
思考总结


  • team_1_name.clone() 是为了避免移动所有权导致变量不可用。
  • 累加进球数和失球数 是因为一个队伍可能会出现在多场角逐中,不能每次都重新初始化数据,而是要在已有的底子上举行更新。
这两者结合起来,能确保正确跟踪每个队伍的进球和失球情况。
总结

通过这三个 HashMap 的实战示例,我们不光把握了如何高效地利用 HashMap 存储和操纵数据,还深入理解了 Rust 的所有权与借用规则在实际开发中的应用。Rust 强调所有权的管理,尤其是在处置惩罚复杂数据布局如 HashMap 时,正确掌控所有权的转移和数据的引用关系至关重要,这不光能够提高代码的效率,还能保障程序的安全性和稳固性。
这些实践展示了 HashMap 在解决实际问题中的强大能力,尤其在需要频繁查找、插入和更新数据的场景中。纯熟把握 HashMap 的利用技巧,将极大提升我们在 Rust 开发中的数据管理效率与程序性能。
参考


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

反转基因福娃

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表