Rust 练习册 99:让数字开口说话

在日常生活中,我们经常需要将数字转换为文字形式,比如在支票上书写金额、语音播报系统或者教育软件中。今天我们要探讨的就是这样一个有趣的问题:如何用Rust将数字转换为英文表达方式。

问题背景

将数字转换为文字看似简单,但实际上涉及许多复杂的规则。英语中数字的表达有特定的规律,比如:

  • 0-19有各自独特的单词
  • 20-99有十位数词缀加上个位数
  • 100以上则需要考虑百位、千位、百万位等

我们的任务是实现这样一个函数:

rust 复制代码
pub fn encode(n: u64) -> String {
    unimplemented!("Say {} in English.", n);
}

这个函数接收一个[u64]类型的数字,返回其英文表达的[String]。

英文数字表达规则

在实现之前,让我们先了解英语中数字表达的基本规则:

  1. 0-19: zero, one, two, three, ..., nineteen
  2. 20-99: twenty, thirty, forty, ..., ninety,复合数用连字符连接,如twenty-one
  3. 100-999: 在百位后加"hundred",如one hundred, two hundred thirty-four
  4. 更大数字: thousand(千), million(百万), billion(十亿), trillion(万亿)等

需要注意的是,美式英语中通常不在百位和十位之间使用"and",这与英式英语有所不同。

解决方案

解决这个问题的关键是分而治之,我们将大数字分解成较小的部分,分别处理后再组合起来。

rust 复制代码
pub fn encode(n: u64) -> String {
    if n == 0 {
        return "zero".to_string();
    }
    
    let mut result = String::new();
    let mut num = n;
    
    // 定义各个数量级的名称
    let scales = [
        (1_000_000_000_000_000_000, "quintillion"),
        (1_000_000_000_000_000, "quadrillion"),
        (1_000_000_000_000, "trillion"),
        (1_000_000_000, "billion"),
        (1_000_000, "million"),
        (1_000, "thousand"),
    ];
    
    // 处理大于等于1000的部分
    for &(scale_value, scale_name) in scales.iter() {
        if num >= scale_value {
            let count = num / scale_value;
            result.push_str(&format_number_under_thousand(count));
            result.push(' ');
            result.push_str(scale_name);
            num %= scale_value;
            
            if num > 0 {
                result.push(' ');
            }
        }
    }
    
    // 处理剩余的小于1000的部分
    if num > 0 {
        result.push_str(&format_number_under_thousand(num));
    }
    
    result
}

// 处理小于1000的数字
fn format_number_under_thousand(n: u64) -> String {
    let mut result = String::new();
    
    // 处理百位
    if n >= 100 {
        result.push_str(&format_small_numbers(n / 100));
        result.push_str(" hundred");
        
        if n % 100 != 0 {
            result.push(' ');
        }
    }
    
    // 处理十位和个位
    if n % 100 != 0 {
        result.push_str(&format_tens(n % 100));
    }
    
    result
}

// 处理0-19的数字
fn format_small_numbers(n: u64) -> &'static str {
    match n {
        0 => "zero",
        1 => "one",
        2 => "two",
        3 => "three",
        4 => "four",
        5 => "five",
        6 => "six",
        7 => "seven",
        8 => "eight",
        9 => "nine",
        10 => "ten",
        11 => "eleven",
        12 => "twelve",
        13 => "thirteen",
        14 => "fourteen",
        15 => "fifteen",
        16 => "sixteen",
        17 => "seventeen",
        18 => "eighteen",
        19 => "nineteen",
        _ => unreachable!(),
    }
}

// 处理20-99的数字
fn format_tens(n: u64) -> String {
    if n < 20 {
        return format_small_numbers(n).to_string();
    }
    
    let tens_place = n / 10;
    let ones_place = n % 10;
    
    let tens_word = match tens_place {
        2 => "twenty",
        3 => "thirty",
        4 => "forty",
        5 => "fifty",
        6 => "sixty",
        7 => "seventy",
        8 => "eighty",
        9 => "ninety",
        _ => unreachable!(),
    };
    
    if ones_place == 0 {
        tens_word.to_string()
    } else {
        format!("{}-{}", tens_word, format_small_numbers(ones_place))
    }
}

测试案例详解

通过查看测试案例,我们可以更好地理解问题的各种情况:

rust 复制代码
#[test]
fn test_zero() {
    assert_eq!(say::encode(0), String::from("zero"));
}

最基本的情况,数字0应该返回"zero"。

rust 复制代码
#[test]
fn test_fourteen() {
    assert_eq!(say::encode(14), String::from("fourteen"));
}

14属于0-19范围内的特殊数字。

rust 复制代码
#[test]
fn test_twenty_two() {
    assert_eq!(say::encode(22), String::from("twenty-two"));
}

22这样的复合数需要用连字符连接。

rust 复制代码
#[test]
fn test_one_hundred_twenty_three() {
    assert_eq!(say::encode(123), String::from("one hundred twenty-three"));
}

三位数需要正确处理百位和其他位之间的空格。

rust 复制代码
#[test]
fn test_987654321123() {
    assert_eq!(
        say::encode(987_654_321_123),
        String::from(
            "nine hundred eighty-seven billion \
             six hundred fifty-four million \
             three hundred twenty-one thousand \
             one hundred twenty-three"
        )
    );
}

更大的数字需要正确处理billion、million、thousand等单位。

rust 复制代码
#[test]
fn test_max_u64() {
    assert_eq!(
        say::encode(18_446_744_073_709_551_615),
        String::from(
            "eighteen quintillion four hundred forty-six \
             quadrillion seven hundred forty-four trillion \
             seventy-three billion seven hundred nine million \
             five hundred fifty-one thousand six hundred fifteen"
        )
    );
}

最大的u64值也需要能够正确处理。

设计思路解析

这个实现采用了一种模块化的方法:

  1. 主函数[encode]: 负责整体流程控制,按数量级分解数字
  2. [format_number_under_thousand]: 处理小于1000的数字
  3. [format_small_numbers]: 处理0-19的基础数字
  4. [format_tens]: 处理20-99的两位数

这种分层设计使代码更清晰易懂,也更容易维护和扩展。

Rust语言特性运用

在这个实现中,我们运用了多种Rust语言特性:

  1. 模式匹配: 使用[match]表达式处理不同的数字范围
  2. 字符串格式化: 使用[format!]宏构建字符串
  3. 静态字符串引用: 对于固定的词汇返回[&'static str]
  4. 类型安全: 利用Rust的类型系统确保不会传入负数

实际应用场景

数字转文字在很多场景下都非常有用:

  1. 金融系统: 支票、合同等正式文档中的金额书写
  2. 语音助手: 数字的语音播报
  3. 教育软件: 数学教学工具
  4. 无障碍技术: 为视障人士提供数字朗读服务
  5. 报表生成: 正式的报告和文档

总结

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

  1. 如何将复杂问题分解为更小的子问题
  2. 如何处理自然语言中的规则和特殊情况
  3. Rust中字符串处理的最佳实践
  4. 模块化设计的重要性

这个问题虽然看起来简单,但涉及许多细节处理,是锻炼编程思维和Rust技能的好题目。通过合理的设计和实现,我们能够构建出一个健壮、高效的数字转文字系统。

相关推荐
子豪-中国机器人1 小时前
C++自定义结构体学习方法:
java·开发语言
August_._1 小时前
【软件安装教程】Node.js 开发环境搭建详解:从安装包下载到全局配置,一篇搞定所有流程
java·vue.js·windows·后端·node.js·配置
7***99871 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
MOMO陌染1 小时前
Java 面向对象之类与对象:编程世界的实体化核心
java·后端
虎子_layor1 小时前
从用户体验出发:分页设计的完整实战指南
java·后端
金銀銅鐵1 小时前
[Java] JDK 15 新变化之文本块(Text Blocks)
java·后端
y***03171 小时前
Spring Boot(快速上手)
java·spring boot·后端
十字路口的火丁1 小时前
通过注册中心实现的 Spring Cloud Gateway 集群化部署,是如何对外访问的?
后端·spring cloud
Penge6661 小时前
NewMatchPhraseQuery 与 NewMultiMatchQuery 原理及实践
后端