使用 HashMap 统计数据

RustBeginner
立即练习

简介

向量通过数字位置组织值。而 HashMap<K, V> 会将 K 类型的每个唯一键与一个 V 类型的值关联起来,因此适合存储计数、设置及其他带键数据。

你将创建一个小型词频映射,读取并替换一个计数,使用 entry API 处理已有单词和缺失单词,并遍历所有值,而不依赖映射未指定的键顺序。

插入并读取带键的值

在此步骤中,插入两个键值对,并安全地读取其中一个计数。

进入项目目录并打开源文件:

cd /home/labex/project/word-counts
nano src/main.rs

预先提供的路径 std::collections::HashMap 用于定位 Rust 标准库集合模块中的 HashMap。use 行为这条较长的路径指定了本地使用的简短名称 HashMap。现在可以将其视为已准备好的导入代码;路径和导入将在后续模块实验中完整讲解。HashMap<String, u32> 用于存储拥有所有权的字符串键和无符号整数计数。将 Step 1a 注释替换为:

    let _ = counts.insert(String::from("rust"), 1);
    let _ = counts.insert(String::from("safe"), 1);

insert 会返回该键之前存储的值。这些键都是新键,因此结果为 None。let _ = ...; 模式会执行操作,并有意丢弃操作结果。单独的 _ 与 _returned_id 这样的名称不同:单独的通配符不会创建可供后续使用的绑定。

将 Step 1b 注释替换为:

    match counts.get("rust") {
        Some(count) => println!("Rust count: {count}"),
        None => println!("Rust count: missing"),
    }

get 接受借用的文本 "rust",并返回 Option<&u32>,不会将键或计数移出映射。保存并退出,然后检查并运行程序:

cargo check
cargo run --quiet
Unique words: 2
Rust count: 1

第一行表明键是唯一的;第二行表明查找成功找到了已存储的计数。

替换已有计数

在此步骤中,对已有键调用 insert,并检查被替换的值。

打开源文件:

nano src/main.rs

将 Step 2 注释替换为:

    let previous = counts.insert(String::from("rust"), 2);
    println!("Previous rust count: {previous:?}");

映射中的每个键只能对应一个值。新值 2 会替换原来的 1,返回的 Some(1) 则让这次替换变得可观察。保存并退出,然后运行:

cargo run --quiet

新增的一行应为:

Previous rust count: Some(1)

唯一单词数仍然是 2,因为替换值不会创建新的键。

使用 Entry API 更新计数

在此步骤中,通过同一个 API,将一个已有单词和一个缺失单词的计数都加 1。

entry(key).or_insert(default) 会返回该键已有值的可变引用;如果键不存在,则插入默认值,并返回新值的可变引用。打开源文件:

nano src/main.rs

将 Step 3 注释替换为:

    *counts.entry(String::from("safe")).or_insert(0) += 1;
    *counts.entry(String::from("fast")).or_insert(0) += 1;
    println!("Safe count: {}", counts["safe"]);
    println!("Fast count: {}", counts["fast"]);

从内向外阅读这段简洁的更新代码。首先,entry(key) 会定位与该键关联的位置,无论该位置已有值还是为空。然后,or_insert(0) 确保该位置存在一个值,并返回指向它的可变引用 &mut u32。

开头的 * 会访问该引用背后的 u32 值。最后,+= 1 将计数加 1,并把新计数存回映射。

因此,safe 会变为 2;缺失的 fast 会先从 0 开始,再变为 1。方括号可以按键读取值。如果键不存在,映射索引会触发 panic;在这里之所以安全,是因为前面的两个 entry 调用保证了这两个键都存在。如果键可能不存在,请继续使用 get。

保存并退出,然后运行:

cargo run --quiet

现在,最后几行应为:

Safe count: 2
Fast count: 1

同一个操作同时处理了已有键和空键两种情况。

遍历时不依赖键顺序

在此步骤中,遍历所有已存储的计数,并计算一个与顺序无关的总数。

哈希映射不保证稳定的键遍历顺序,因此面向初学者的验证不应要求键按照特定顺序输出。values() 方法会访问每个已存储的值,因此足以计算总数。

打开源文件并替换 Step 4 注释:

nano src/main.rs
    let mut total = 0;
    for count in counts.values() {
        total += count;
    }
    println!("Total occurrences: {total}");

这里的 count 是一个共享引用,+= 可以将该引用指向的整数相加。保存并退出,然后检查并运行程序:

cargo check
cargo run --quiet

最后一行应为:

Total occurrences: 5

总数由 rust = 2、safe = 2 和 fast = 1 相加得到,不依赖它们的遍历顺序。

总结

你插入、读取、替换并递增了带键的计数,然后遍历了映射中的所有值,而没有假设键的顺序稳定。