Rust 练习册 116:杂志剪贴侦探游戏

在数字时代,我们很少再看到用剪贴杂志制作便签或勒索信的场景,但这个经典的概念却是一个极好的编程练习题目。今天,我们将通过 Rust 实现一个"杂志剪贴"检测器,来学习 Rust 中集合类型和算法思维的强大功能。

问题场景

想象一下,你是一名侦探,正在调查一起神秘案件。现场发现了一张手工制作的便签,而你需要确定嫌疑人在案发时是否接触过某本特定的杂志。你的任务是判断这本杂志中的单词是否足够制作出这张便签。

这个问题看似简单,但背后涉及了重要的计算机科学概念------字符频率统计和资源分配问题。

核心实现

rust 复制代码
// This stub file contains items which aren't used yet; feel free to remove this module attribute
// to enable stricter warnings.
#![allow(unused)]

use std::collections::HashMap;

pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
    let mut word_count: HashMap<&str, u32> = HashMap::new();
    
    // 统计杂志中每个单词的数量
    for &word in magazine {
        *word_count.entry(word).or_insert(0) += 1;
    }
    
    // 检查便签中的每个单词是否都能从杂志中找到
    for &word in note {
        let count = word_count.entry(word).or_insert(0);
        if *count == 0 {
            return false;
        }
        *count -= 1;
    }
    
    true
}

代码解析

HashMap 的使用

rust 复制代码
use std::collections::HashMap;

let mut word_count: HashMap<&str, u32> = HashMap::new();

我们使用 HashMap 来存储每个单词及其出现次数。键是单词(&str),值是计数(u32)。这是解决频率统计问题的经典数据结构。

entry API 的妙用

rust 复制代码
*word_count.entry(word).or_insert(0) += 1;

Rust 的 entry API 是处理 HashMap 的优雅方式。它避免了多次查找同一个键的开销:

  • 如果键存在,entry() 返回一个 OccupiedEntry
  • 如果键不存在,or_insert(0) 插入默认值 0

然后通过解引用 * 直接修改值。

引用和解引用

rust 复制代码
for &word in magazine {
    // ...
}

这里使用了模式匹配来解构引用。因为 magazine&[&str] 类型,所以其中的元素是 &&str。通过 &word 模式,我们直接获取了 &str 类型的 [word](file:///Users/zacksleo/projects/github/zacksleo/exercism-rust/exercises/concept/semi-structured-logs/src/lib.rs#L25-L25)。

算法逻辑

rust 复制代码
// 统计杂志中每个单词的数量
for &word in magazine {
    *word_count.entry(word).or_insert(0) += 1;
}

// 检查便签中的每个单词是否都能从杂志中找到
for &word in note {
    let count = word_count.entry(word).or_insert(0);
    if *count == 0 {
        return false;
    }
    *count -= 1;
}

算法分为两个步骤:

  1. 统计杂志中每个单词的出现次数
  2. 遍历便签中的单词,检查是否有足够的单词可用,同时减少相应的计数

测试用例分析

通过测试用例我们可以看到各种场景:

rust 复制代码
use magazine_cutout::*;

#[test]
fn test_example_works() {
    let magazine = "two times three is not four"
        .split_whitespace()
        .collect::<Vec<&str>>();
    let note = "two times two is four"
        .split_whitespace()
        .collect::<Vec<&str>>();
    assert!(!can_construct_note(&magazine, &note));
}

#[test]
fn test_fn_returns_true_for_good_input() {
    let magazine = "The metro orchestra unveiled its new grand piano today. Its donor paraphrased Nathn Hale: \"I only regret that I have but one to give \"".split_whitespace().collect::<Vec<&str>>();
    let note = "give one grand today."
        .split_whitespace()
        .collect::<Vec<&str>>();
    assert!(can_construct_note(&magazine, &note));
}

#[test]
fn test_fn_returns_false_for_bad_input() {
    let magazine = "I've got a lovely bunch of coconuts."
        .split_whitespace()
        .collect::<Vec<&str>>();
    let note = "I've got som coconuts"
        .split_whitespace()
        .collect::<Vec<&str>>();
    assert!(!can_construct_note(&magazine, &note));
}

#[test]
fn test_case_sensitivity() {
    let magazine = "i've got some lovely coconuts"
        .split_whitespace()
        .collect::<Vec<&str>>();
    let note = "I've got some coconuts"
        .split_whitespace()
        .collect::<Vec<&str>>();
    assert!(!can_construct_note(&magazine, &note));

    let magazine = "I've got some lovely coconuts"
        .split_whitespace()
        .collect::<Vec<&str>>();
    let note = "i've got some coconuts"
        .split_whitespace()
        .collect::<Vec<&str>>();
    assert!(!can_construct_note(&magazine, &note));
}

这些测试用例覆盖了多种情况:

  • 基本功能测试
  • 正面案例(可以构造)
  • 负面案例(无法构造)
  • 大小写敏感性测试

Rust 特性的体现

1. 零成本集合类型

rust 复制代码
use std::collections::HashMap;

Rust 标准库提供了高性能的集合类型,这些实现在保证安全的同时,性能与 C++ 的标准库相当。

2. 模式匹配解构

rust 复制代码
for &word in magazine {
    // ...
}

通过模式匹配,我们可以直接获取需要的值,避免了额外的解引用操作。

3. 所有权和借用系统

rust 复制代码
pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool

函数参数使用切片引用,避免了不必要的数据拷贝,同时保证了内存安全。

4. 错误处理和提前返回

rust 复制代码
if *count == 0 {
    return false;
}

一旦发现无法满足需求,立即返回 false,避免了不必要的计算。

实际应用场景

这个算法在现实世界中有许多应用:

  1. 文本分析:检查文档相似性,检测抄袭
  2. 资源分配:验证系统是否有足够的资源执行任务
  3. 库存管理:检查仓库是否有足够的商品满足订单
  4. 游戏开发:检查玩家是否有足够的材料制作物品
  5. 编译器优化:寄存器分配和资源调度

性能优化考虑

对于大规模数据,我们可以进一步优化:

rust 复制代码
pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
    // 如果便签比杂志还长,直接返回false
    if note.len() > magazine.len() {
        return false;
    }
    
    let mut word_count: HashMap<&str, u32> = HashMap::new();
    
    // ... 其余逻辑保持不变
}

扩展思考

这个问题还可以有多种变化:

  1. 大小写不敏感:统一转换为小写再比较
  2. 标点符号处理:去除标点符号
  3. 词干提取:将单词还原为词根进行比较
  4. 多语言支持:处理不同语言的文本

总结

通过这个练习,我们学习了:

  • HashMap 的基本使用和 entry API
  • 模式匹配在解构中的应用
  • 引用和解引用的操作
  • 算法思维在实际问题中的应用
  • Rust 在文本处理方面的优势

这个看似简单的"杂志剪贴"问题,实际上展示了 Rust 在处理复杂数据结构时的优雅和高效。通过合理使用标准库提供的工具,我们可以用很少的代码解决实际问题。

在下一篇文章中,我们将继续探索 Rust 的更多强大功能!

相关推荐
通往曙光的路上1 小时前
异步任务la
java·开发语言
by__csdn1 小时前
第一章 (ASP.NET Core入门)第一节( 认识.NET Core)
后端·c#·asp.net·.net·.netcore·f#·vb.net
by__csdn2 小时前
第一章 (ASP.NET Core入门)第二节( 认识ASP.NET Core)
数据库·后端·c#·asp.net·.net·.netcore·f#
+++.2 小时前
c++雪花屏(vsCode+cmake+mingw+ninja)
开发语言·c++·vscode
小年糕是糕手2 小时前
【C++】内存管理(下)
java·c语言·开发语言·数据结构·c++·算法
缺点内向2 小时前
如何使用C#将Excel工作表拆分为独立文件
开发语言·c#·.net·excel
L.EscaRC2 小时前
Spring Boot开发中加密数据的模糊搜索
java·spring boot·后端
csbysj20202 小时前
jEasyUI 自定义排序
开发语言
编织幻境的妖2 小时前
Python对象序列化和反序列化方法总结
开发语言·python