首页 > 其他分享 >Rust 中的 HashMap 实战指南:理解与优化技巧

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

时间:2024-10-09 19:50:25浏览次数:1  
标签:指南 HashMap fruit let goals team basket Rust

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

在 Rust 编程中,HashMap 是一个强大的键值对数据结构,广泛应用于数据统计、信息存储等场景。在本文中,我们将通过三个实际的代码示例,详细讲解 HashMap 的基本用法以及如何在真实项目中充分利用它。此外,我们还将探讨 Rust 的所有权系统对 HashMap 的影响,并分享避免常见陷阱的技巧。

本文通过三个 Rust 实战例子,展示了 HashMap 的基本用法及其在实际场景中的应用。我们将从简单的水果篮子示例出发,逐步演示如何使用 HashMap 存储和处理不同数据,并通过添加测试用例来确保代码的正确性。此外,我们还会深入探讨 Rust 所有权系统对 HashMap 使用的影响,尤其是如何避免所有权转移的问题。

实操

示例一:使用 HashMap 存储水果篮子

// hashmaps1.rs
//
// A basket of fruits in the form of a hash map needs to be defined. The key
// represents the name of the fruit and the value represents how many of that
// particular fruit is in the basket. You have to put at least three different
// types of fruits (e.g apple, banana, mango) in the basket and the total count
// of all the fruits should be at least five.
//
// Make me compile and pass the tests!
//
// Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a
// hint.

use std::collections::HashMap;

fn fruit_basket() -> HashMap<String, u32> {
    let mut basket = HashMap::new(); // TODO: declare your hash map here.

    // Two bananas are already given for you :)
    basket.insert(String::from("banana"), 2);

    // TODO: Put more fruits in your basket here.
    basket.insert(String::from("apple"), 3);
    basket.insert(String::from("mango"), 4);
    basket.insert(String::from("orange"), 5);
    basket
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn at_least_three_types_of_fruits() {
        let basket = fruit_basket();
        assert!(basket.len() >= 3);
    }

    #[test]
    fn at_least_five_fruits() {
        let basket = fruit_basket();
        assert!(basket.values().sum::<u32>() >= 5);
    }
}

在 本例中,我们构建了一个水果篮子,并通过 HashMap 来存储水果种类及其数量。

通过测试,我们验证了篮子中至少有三种水果,并且总数超过五个。

示例二:不重复添加水果

// hashmaps2.rs
//
// We're collecting different fruits to bake a delicious fruit cake. For this,
// we have a basket, which we'll represent in the form of a hash map. The key
// represents the name of each fruit we collect and the value represents how
// many of that particular fruit we have collected. Three types of fruits -
// Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You
// must add fruit to the basket so that there is at least one of each kind and
// more than 11 in total - we have a lot of mouths to feed. You are not allowed
// to insert any more of these fruits!
//
// Make me pass the tests!
//
// Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a
// hint.

use std::collections::HashMap;

#[derive(Hash, PartialEq, Eq)]
enum Fruit {
    Apple,
    Banana,
    Mango,
    Lychee,
    Pineapple,
}

fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
    let fruit_kinds = vec![
        Fruit::Apple,
        Fruit::Banana,
        Fruit::Mango,
        Fruit::Lychee,
        Fruit::Pineapple,
    ];

    for fruit in fruit_kinds {
        // TODO: Insert new fruits if they are not already present in the
        // basket. Note that you are not allowed to put any type of fruit that's
        // already present!
        *basket.entry(fruit).or_insert(1);
        // 如果水果不在篮子中,则插入数量为1的该水果
        // if !basket.contains_key(&fruit) {
        //     basket.insert(fruit, 1);
        // }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Don't modify this function!
    fn get_fruit_basket() -> HashMap<Fruit, u32> {
        let mut basket = HashMap::<Fruit, u32>::new();
        basket.insert(Fruit::Apple, 4);
        basket.insert(Fruit::Mango, 2);
        basket.insert(Fruit::Lychee, 5);

        basket
    }

    #[test]
    fn test_given_fruits_are_not_modified() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
        assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
        assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
    }

    #[test]
    fn at_least_five_types_of_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count_fruit_kinds = basket.len();
        assert!(count_fruit_kinds >= 5);
    }

    #[test]
    fn greater_than_eleven_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count = basket.values().sum::<u32>();
        assert!(count > 11);
    }

    #[test]
    fn all_fruit_types_in_basket() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        for amount in basket.values() {
            assert_ne!(amount, &0);
        }
    }
}

在上面的示例代码中,我们通过 HashMap 存储多个水果,但避免重复添加已有的水果种类。

测试用例验证了我们不会修改已存在的水果,并确保总数超过 11 个。

示例三:记录比赛比分

// hashmaps3.rs
//
// A list of scores (one per line) of a soccer match is given. Each line is of
// the form : "<team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>"
// Example: England,France,4,2 (England scored 4 goals, France 2).
//
// You have to build a scores table containing the name of the team, goals the
// team scored, and goals the team conceded. One approach to build the scores
// table is to use a Hashmap. The solution is partially written to use a
// Hashmap, complete it to pass the test.
//
// Make me pass the tests!
//
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a
// hint.

use std::collections::HashMap;

// A structure to store the goal details of a 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();
        // TODO: Populate the scores table with details extracted from the
        // current line. Keep in mind that goals scored by team_1
        // will be the number of goals conceded from team_2, and similarly
        // goals scored by team_2 will be the number of goals conceded by
        // team_1.

        // 更新 team_1 的数据
        let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
            goals_scored: 0,
            goals_conceded: 0,
        });
        team_1.goals_scored += team_1_score;
        team_1.goals_conceded += team_2_score;

        // 更新 team_2 的数据
        let team_2 = scores.entry(team_2_name.clone()).or_insert(Team {
            goals_scored: 0,
            goals_conceded: 0,
        });
        team_2.goals_scored += team_2_score;
        team_2.goals_conceded += team_1_score;
    }
    scores
}

#[cfg(test)]
mod tests {
    use super::*;

    fn get_results() -> String {
        let results = "".to_string()
            + "England,France,4,2\n"
            + "France,Italy,3,1\n"
            + "Poland,Spain,2,0\n"
            + "Germany,England,2,1\n";
        results
    }

    #[test]
    fn build_scores() {
        let scores = build_scores_table(get_results());

        let mut keys: Vec<&String> = scores.keys().collect();
        keys.sort();
        assert_eq!(
            keys,
            vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
        );
    }

    #[test]
    fn validate_team_score_1() {
        let scores = build_scores_table(get_results());
        let team = scores.get("England").unwrap();
        assert_eq!(team.goals_scored, 5);
        assert_eq!(team.goals_conceded, 4);
    }

    #[test]
    fn validate_team_score_2() {
        let scores = build_scores_table(get_results());
        let team = scores.get("Spain").unwrap();
        assert_eq!(team.goals_scored, 0);
        assert_eq!(team.goals_conceded, 2);
    }
}

本示例展示了 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() 的目的是避免所有权转移而导致变量不可用。

示例:

let team_1_name = "England".to_string();
// 所有权被移动给 entry(),你不能再访问 team_1_name
scores.entry(team_1_name); 

// 如果你还想用 team_1_name,就要使用 clone():
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_scoredgoals_conceded 字段。

通过这种方式,每次遇到相同队伍时,不会重新初始化,而是将新的进球和失球数累加到已有数据中。

let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
    goals_scored: 0,
    goals_conceded: 0,
});

// 累加进球和失球
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;

这样就能保证每个队伍的分数在不同比赛中是累积的,而不是被覆盖掉。

思考总结

  • team_1_name.clone() 是为了避免移动所有权导致变量不可用。
  • 累加进球数和失球数 是因为一个队伍可能会出现在多场比赛中,不能每次都重新初始化数据,而是要在已有的基础上进行更新。

这两者结合起来,能确保正确跟踪每个队伍的进球和失球情况。

总结

通过这三个 HashMap 的实战示例,我们不仅掌握了如何高效地使用 HashMap 存储和操作数据,还深入理解了 Rust 的所有权与借用规则在实际开发中的应用。Rust 强调所有权的管理,尤其是在处理复杂数据结构如 HashMap 时,准确掌控所有权的转移和数据的引用关系至关重要,这不仅能够提高代码的效率,还能保障程序的安全性和稳定性。

这些实践展示了 HashMap 在解决实际问题中的强大能力,尤其在需要频繁查找、插入和更新数据的场景中。熟练掌握 HashMap 的使用技巧,将极大提升我们在 Rust 开发中的数据管理效率与程序性能。

参考

标签:指南,HashMap,fruit,let,goals,team,basket,Rust
From: https://www.cnblogs.com/QiaoPengjun/p/18455019

相关文章

  • xtrabackup备份工具使用指南
    一、xtrabackup介绍xtrabackup是由Percona公司开发的一个用于MySQL数据库物理热备的工具,开源免费。目前最新的xtrabbackup8.3版本可以备份MySQL8.3servers上的InnoDB,XtraDB,MyISAM,MyRocks表,PerconaServerforMySQLwithXtraDB,PerconaServerforMySQL8.3......
  • MongoDB分片键选择指南
    MongoDB分片键选择指南特别是华为Mongodb4.0集群的使用,更能提高查询效率MongoDB是一款高性能的NoSQL数据库,能够处理大量数据并支持水平扩展。为了实现这一点,MongoDB使用了分片技术,而选择合适的分片键对性能和可伸缩性有着至关重要的影响。第一步:理解分片的工作原理MongoDB的......
  • WIN11 vmmare下 ubuntu 的安装及配置(附错误指南)
    写在前面:本文章基于老师发的安装配置说明书,结合自己在安装和配置过程中遇到的问题,阐述该如何进行安装配置ubuntu以及个人遇到的问题,若对你有帮助还请点赞支持!相信文章不会让你失望!具体步骤见链接:参考文章:Win11的Ubuntu的安装及配置(超级详细)Win11的Ubuntu的安装及配置(超级详......
  • Mac 开发环境快速搭建指南,提升效率必备
    ......
  • Postman 教程:新手必备的操作指南
    API已经成为连接不同系统和服务的重要桥梁,无论你是前端开发者、后端工程师还是测试人员,掌握API的开发和测试技能都是非常重要的。Postman是一个广受欢迎的API开发工具,它不仅能够帮助你轻松发送HTTP请求,还提供了强大的测试、调试和协作功能。 本系列教程旨在帮助你从零......
  • DeepLearning.ai专项课程总结:深度学习入门指南
    DeepLearning.ai-SummaryDeepLearning.ai专项课程:深度学习的最佳入门之选DeepLearning.ai是由斯坦福大学教授AndrewNg在Coursera平台上推出的一个深度学习专项课程。作为人工智能和机器学习领域的顶级专家,AndrewNg精心设计了这一系列课程,旨在帮助学习者系统地掌握深度学习......
  • 洛谷题单指南-字符串-P3375 【模板】KMP
    原题链接:https://www.luogu.com.cn/problem/P3375题意解读:给定两个字符串:原串s,模式串p,求p在s中出现的所有位置,并输出p的长度为1~p.size()的子串的最长相同真前、后缀的长度。解题思路:KMP模版题,分两问,第一问通过KMP核心算法实现,第二问输出模式串的Next数组内容,接下来一一解读。......
  • iPaaS全面选型指南
    专业iPaaS厂商产品方案专业iPaaS厂商指的是只专注于投入到iPaaS产品研发的创新型企业(如RestCloud)。专业公司不仅具备了丰富的行业集成经验和深厚的专业知识,更以其独特的视角和专注的态度,成为了iPaaS领域的佼佼者。相比于那些综合性软件企业,他们更加专注于iPaaS产品的研发和创新......
  • d3drm.dll文件丢失修复指南:恢复游戏和软件正常运行
    d3drm.dll是DirectX3DRetainedMode(Direct3D保留模式)的一部分,这种模式在较旧的DirectX版本中使用。如果你遇到这个DLL文件缺失的问题,可以尝试以下几种方法来解决:重新安装或更新DirectX修复工具:访问微软官方网站下载并安装最新的DirectX修复工具。如果你运行的是较老的游......
  • 高效开发最佳实践全面指南
    学会表达在写复杂表达式时,可使用一个变量将表达式用变量的方式表示函数、变量命名要语义化学会复盘花一些时间清理自己的代码尽量以函数式进行编程拥抱变化在开发功能时,要考虑变化的情况。该死的产品经理在封装时要考虑能否封装成一个js模块,后续只需要调用响应的......