在现代科技世界中,机器人已成为我们日常生活的重要组成部分。无论是工业制造还是家庭助手,每个机器人都需要一个独特的身份标识。今天,我们将学习如何使用 Rust 为机器人生成唯一的名称,这不仅是编程练习,更是对现实世界问题的模拟。

问题描述

我们的任务是为机器人生成符合特定格式的唯一名称:

  • 名称由 5 个字符组成
  • 前两个字符是大写字母(A-Z)
  • 后三个字符是数字(0-9)
  • 每个机器人必须拥有唯一的名称
  • 机器人应该能够重置自己的名称

例如:“AB123”、“XY987” 都是有效的机器人名称。

核心实现

让我们来看看机器人的基本结构和方法:

pub struct Robot;

impl Robot {
    pub fn new() -> Self {
        unimplemented!("Construct a new Robot struct.");
    }

    pub fn name(&self) -> &str {
        unimplemented!("Return the reference to the robot's name.");
    }

    pub fn reset_name(&mut self) {
        unimplemented!("Assign a new unique name to the robot.");
    }
}

这是一个典型的面向对象设计,[Robot](file:///Users/zacksleo/projects/github/zacksleo/exercism-rust/exercises/practice/docs/robot-name.md#L2-L50) 结构体包含创建、获取名称和重置名称的方法。

完整实现方案

为了满足所有要求,我们需要跟踪已使用的名称并生成新的唯一名称。以下是完整的实现:

use std::collections::HashSet;
use std::sync::Mutex;
use lazy_static::lazy_static;
use rand::{thread_rng, Rng};

lazy_static! {
    static ref USED_NAMES: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
}

pub struct Robot {
    name: String,
}

impl Robot {
    pub fn new() -> Self {
        let mut robot = Robot { name: String::new() };
        robot.reset_name();
        robot
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn reset_name(&mut self) {
        let mut used_names = USED_NAMES.lock().unwrap();
        
        // 如果这个机器人已经有名字,先释放它
        if !self.name.is_empty() {
            used_names.remove(&self.name);
        }
        
        // 生成新的唯一名称
        loop {
            let name = generate_random_name();
            if !used_names.contains(&name) {
                self.name = name;
                used_names.insert(self.name.clone());
                break;
            }
        }
    }
}

fn generate_random_name() -> String {
    let mut rng = thread_rng();
    
    // 生成前两个大写字母
    let letters: String = (0..2)
        .map(|_| rng.gen_range(b'A'..=b'Z') as char)
        .collect();
    
    // 生成后三个数字
    let numbers: String = (0..3)
        .map(|_| rng.gen_range(0..=9).to_string())
        .collect();
    
    format!("{}{}", letters, numbers)
}

关键技术点解析

1. 全局状态管理

由于我们需要确保所有机器人的名称都是唯一的,我们必须维护一个全局的已使用名称集合:

use std::collections::HashSet;
use std::sync::Mutex;
use lazy_static::lazy_static;

lazy_static! {
    static ref USED_NAMES: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
}

这里我们使用了 [lazy_static](file:///Users/zacksleo/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lazy_static-1.4.0/src/lib.rs#L1-L733) 宏来创建一个全局静态变量,它是一个线程安全的互斥锁保护的哈希集合。

2. 名称生成算法

fn generate_random_name() -> String {
    let mut rng = thread_rng();
    
    // 生成前两个大写字母
    let letters: String = (0..2)
        .map(|_| rng.gen_range(b'A'..=b'Z') as char)
        .collect();
    
    // 生成后三个数字
    let numbers: String = (0..3)
        .map(|_| rng.gen_range(0..=9).to_string())
        .collect();
    
    format!("{}{}", letters, numbers)
}

这个函数使用随机数生成器创建符合指定格式的名称。

3. 线程安全与所有权

pub fn reset_name(&mut self) {
    let mut used_names = USED_NAMES.lock().unwrap();
    
    // 如果这个机器人已经有名字,先释放它
    if !self.name.is_empty() {
        used_names.remove(&self.name);
    }
    
    // 生成新的唯一名称
    loop {
        let name = generate_random_name();
        if !used_names.contains(&name) {
            self.name = name;
            used_names.insert(self.name.clone());
            break;
        }
    }
}

在重置名称时,我们需要获取全局名称集合的锁,确保操作的原子性。这也体现了 Rust 在并发编程中的安全性保证。

测试验证

为了确保我们的实现正确无误,我们编写了全面的测试用例:

fn assert_name_matches_pattern(n: &str) {
    assert!(n.len() == 5, "name is exactly 5 characters long");
    assert!(
        n[0..2].chars().all(|c| ('A'..='Z').contains(&c)),
        "name starts with 2 uppercase letters"
    );
    assert!(
        n[2..].chars().all(|c| ('0'..='9').contains(&c)),
        "name ends with 3 numbers"
    );
}

#[test]
fn test_name_should_match_expected_pattern() {
    let r = robot::Robot::new();
    assert_name_matches_pattern(r.name());
}

#[test]
#[ignore]
fn test_different_robots_have_different_names() {
    let r1 = robot::Robot::new();
    let r2 = robot::Robot::new();
    assert_ne!(r1.name(), r2.name(), "Robot names should be different");
}

#[test]
#[ignore]
fn test_many_different_robots_have_different_names() {
    use std::collections::HashSet;

    // In 3,529 random robot names, there is ~99.99% chance of a name collision
    let vec: Vec<_> = (0..3529).map(|_| robot::Robot::new()).collect();
    let set: HashSet<_> = vec.iter().map(|robot| robot.name()).collect();

    let number_of_collisions = vec.len() - set.len();
    assert_eq!(number_of_collisions, 0);
}

这些测试验证了:

  1. 名称格式正确性
  2. 不同机器人拥有不同名称
  3. 大量机器人也不会出现名称冲突

Rust 特性的体现

这个练习很好地展示了 Rust 的多个重要特性:

  1. 所有权系统:通过合理的借用和移动确保内存安全
  2. 并发安全:使用 Mutex 提供线程安全的全局状态访问
  3. 错误处理:通过 Option 和 Result 类型处理潜在的错误情况
  4. 外部库集成:使用 rand 和 lazy_static 等流行 crate
  5. 模式匹配:在测试中使用范围匹配验证字符

总结

机器人命名问题虽然表面上看起来简单,但实际上涉及到了许多重要的编程概念,包括全局状态管理、并发控制、随机数生成和唯一性保证等。通过 Rust 强大的类型系统和所有权模型,我们能够编写出既安全又高效的实现。

这个练习告诉我们,在面对真实世界的编程挑战时,Rust 提供了强大的工具来帮助我们写出正确、高效的代码。无论是处理全局状态还是并发访问,Rust 的编译器都能在编译期就帮我们发现潜在的问题,这是传统语言很难做到的。

掌握了这些技巧,你就能够在自己的项目中更好地处理类似的资源管理和并发控制问题。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐