当前位置: 首页 > news >正文

Rust 练习册 :Phone Number与电话号码处理

电话号码处理是现代软件开发中常见的需求,特别是在通讯、社交、电商等应用中。在 Exercism 的 “phone-number” 练习中,我们需要实现一个函数来清理和验证北美电话号码(NANP - North American Numbering Plan)。这不仅能帮助我们掌握字符串处理和正则表达式技巧,还能深入学习Rust中的错误处理和数据验证。

什么是北美电话号码?

北美电话号码遵循北美编号计划(NANP),格式为:(NXX) NXX-XXXX,其中:

  • 第一部分(3位)是地区代码(Area Code)
  • 第二部分(3位)是交换代码(Exchange Code)
  • 第三部分(4位)是号码(Number)

有效的NANP电话号码需要满足以下条件:

  1. 总共10位数字(不包括国家代码)
  2. 如果有11位数字,第一位必须是1(美国国家代码)
  3. 地区代码不能以0或1开头
  4. 交换代码不能以0或1开头

让我们先看看练习提供的函数签名:

pub fn number(user_number: &str) -> Option<String> {unimplemented!("Given the number entered by user '{}', convert it into SMS-friendly format. If the entered number is not a valid NANP number, return None.",user_number);
}

我们需要实现number函数,将用户输入的电话号码转换为标准格式,如果输入无效则返回None。

设计分析

1. 核心要求

  1. 数据清理:从用户输入中提取数字,去除所有非数字字符
  2. 格式验证:验证电话号码是否符合NANP标准
  3. 长度检查:检查电话号码长度是否正确
  4. 数字验证:验证地区代码和交换代码的首位不能是0或1

2. 技术要点

  1. 字符串处理:高效处理和过滤字符串中的字符
  2. 正则表达式:使用正则表达式进行模式匹配和提取
  3. 错误处理:使用Option类型处理无效输入
  4. 数据验证:实现复杂的业务规则验证

完整实现

1. 基础实现

pub fn number(user_number: &str) -> Option<String> {// 提取所有数字字符let digits: String = user_number.chars().filter(|c| c.is_ascii_digit()).collect();// 根据数字长度进行处理match digits.len() {10 => {// 检查地区代码和交换代码if is_valid_area_code(&digits[0..3]) && is_valid_exchange_code(&digits[3..6]) {Some(digits)} else {None}}11 => {// 检查第一位是否为1if digits.starts_with('1') {let number_part = &digits[1..];if is_valid_area_code(&number_part[0..3]) && is_valid_exchange_code(&number_part[3..6]) {Some(number_part.to_string())} else {None}} else {None}}_ => None,}
}fn is_valid_area_code(area_code: &str) -> bool {// 地区代码不能以0或1开头!area_code.starts_with('0') && !area_code.starts_with('1')
}fn is_valid_exchange_code(exchange_code: &str) -> bool {// 交换代码不能以0或1开头!exchange_code.starts_with('0') && !exchange_code.starts_with('1')
}

2. 优化实现

pub fn number(user_number: &str) -> Option<String> {// 提取所有数字字符let digits: String = user_number.chars().filter(|c| c.is_ascii_digit()).collect();// 验证并处理电话号码validate_and_format(digits)
}fn validate_and_format(digits: String) -> Option<String> {match digits.len() {10 => {// 直接验证10位号码validate_ten_digit_number(&digits)}11 => {// 验证11位号码,第一位必须是1if digits.starts_with('1') {validate_ten_digit_number(&digits[1..])} else {None}}_ => None,}
}fn validate_ten_digit_number(digits: &str) -> Option<String> {// 检查地区代码(前3位)和交换代码(第4-6位)let area_code = &digits[0..3];let exchange_code = &digits[3..6];if is_valid_area_code(area_code) && is_valid_exchange_code(exchange_code) {Some(digits.to_string())} else {None}
}fn is_valid_area_code(area_code: &str) -> bool {// 地区代码不能以0或1开头!area_code.starts_with('0') && !area_code.starts_with('1')
}fn is_valid_exchange_code(exchange_code: &str) -> bool {// 交换代码不能以0或1开头!exchange_code.starts_with('0') && !exchange_code.starts_with('1')
}

3. 使用正则表达式的实现

pub fn number(user_number: &str) -> Option<String> {use regex::Regex;// 移除所有非数字字符let re = Regex::new(r"\D").unwrap();let digits = re.replace_all(user_number, "").to_string();// 验证并格式化电话号码validate_and_format(digits)
}fn validate_and_format(digits: String) -> Option<String> {match digits.len() {10 => {validate_ten_digit_number(&digits)}11 => {if digits.starts_with('1') {validate_ten_digit_number(&digits[1..])} else {None}}_ => None,}
}fn validate_ten_digit_number(digits: &str) -> Option<String> {let area_code = &digits[0..3];let exchange_code = &digits[3..6];if is_valid_area_code(area_code) && is_valid_exchange_code(exchange_code) {Some(digits.to_string())} else {None}
}fn is_valid_area_code(area_code: &str) -> bool {!area_code.starts_with('0') && !area_code.starts_with('1')
}fn is_valid_exchange_code(exchange_code: &str) -> bool {!exchange_code.starts_with('0') && !exchange_code.starts_with('1')
}

测试用例分析

通过查看测试用例,我们可以更好地理解需求:

#[test]
fn test_cleans_the_number() {process_clean_case("(223) 456-7890", Some("2234567890"));
}

应该清理括号、空格和连字符等字符。

#[test]
fn test_cleans_numbers_with_dots() {process_clean_case("223.456.7890", Some("2234567890"));
}

应该清理点号等分隔符。

#[test]
fn test_cleans_numbers_with_multiple_spaces() {process_clean_case("223 456   7890   ", Some("2234567890"));
}

应该清理多余的空格。

#[test]
fn test_invalid_when_9_digits() {process_clean_case("123456789", None);
}

9位数字是无效的。

#[test]
fn test_invalid_when_11_digits_does_not_start_with_a_1() {process_clean_case("22234567890", None);
}

11位数字但不以1开头是无效的。

#[test]
fn test_valid_when_11_digits_and_starting_with_1() {process_clean_case("12234567890", Some("2234567890"));
}

11位数字且以1开头是有效的,应移除前导1。

#[test]
fn test_valid_when_11_digits_and_starting_with_1_even_with_punctuation() {process_clean_case("+1 (223) 456-7890", Some("2234567890"));
}

带有标点符号的11位数字且以+1开头是有效的。

#[test]
fn test_invalid_when_more_than_11_digits() {process_clean_case("321234567890", None);
}

超过11位数字是无效的。

#[test]
fn test_invalid_with_letters() {process_clean_case("123-abc-7890", None);
}

包含字母是无效的。

#[test]
fn test_invalid_with_punctuations() {process_clean_case("123-@:!-7890", None);
}

包含特殊标点符号是无效的。

#[test]
fn test_invalid_if_area_code_starts_with_1_on_valid_11digit_number() {process_clean_case("1 (123) 456-7890", None);
}

地区代码以1开头是无效的。

#[test]
fn test_invalid_if_area_code_starts_with_0_on_valid_11digit_number() {process_clean_case("1 (023) 456-7890", None);
}

地区代码以0开头是无效的。

#[test]
fn test_invalid_if_area_code_starts_with_1() {process_clean_case("(123) 456-7890", None);
}

地区代码以1开头是无效的。

#[test]
fn test_invalid_if_exchange_code_starts_with_1() {process_clean_case("(223) 156-7890", None);
}

交换代码以1开头是无效的。

#[test]
fn test_invalid_if_exchange_code_starts_with_0() {process_clean_case("(223) 056-7890", None);
}

交换代码以0开头是无效的。

#[test]
fn test_invalid_if_exchange_code_starts_with_1_on_valid_11digit_number() {process_clean_case("1 (223) 156-7890", None);
}

交换代码以1开头是无效的。

#[test]
fn test_invalid_if_exchange_code_starts_with_0_on_valid_11digit_number() {process_clean_case("1 (223) 056-7890", None);
}

交换代码以0开头是无效的。

#[test]
fn test_invalid_if_area_code_starts_with_0() {process_clean_case("(023) 456-7890", None);
}

地区代码以0开头是无效的。

性能优化版本

考虑性能的优化实现:

pub fn number(user_number: &str) -> Option<String> {// 预分配字符串容量以避免重新分配let mut digits = String::with_capacity(11);// 手动迭代字符以提高性能for c in user_number.chars() {if c.is_ascii_digit() {digits.push(c);}}// 验证并格式化电话号码validate_and_format_optimized(digits)
}fn validate_and_format_optimized(digits: String) -> Option<String> {match digits.len() {10 => {validate_ten_digit_number_optimized(&digits)}11 => {// 检查第一位是否为1(使用索引而不是starts_with以提高性能)if unsafe { digits.as_bytes().get_unchecked(0) == &b'1' } {validate_ten_digit_number_optimized(&digits[1..])} else {None}}_ => None,}
}fn validate_ten_digit_number_optimized(digits: &str) -> Option<String> {// 使用字节比较以提高性能let bytes = digits.as_bytes();// 检查地区代码(前3位)首位不能是0或1if bytes[0] == b'0' || bytes[0] == b'1' {return None;}// 检查交换代码(第4-6位)首位不能是0或1if bytes[3] == b'0' || bytes[3] == b'1' {return None;}Some(digits.to_string())
}// 使用预编译正则表达式的版本
use regex::Regex;
use std::sync::OnceLock;fn get_digit_regex() -> &'static Regex {static REGEX: OnceLock<Regex> = OnceLock::new();REGEX.get_or_init(|| Regex::new(r"\D").unwrap())
}pub fn number_with_regex(user_number: &str) -> Option<String> {let re = get_digit_regex();let digits = re.replace_all(user_number, "").to_string();validate_and_format_optimized(digits)
}

错误处理和边界情况

考虑更多边界情况的实现:

pub fn number(user_number: &str) -> Option<String> {// 处理空字符串if user_number.is_empty() {return None;}// 提取所有数字字符let digits: String = user_number.chars().filter(|c| c.is_ascii_digit()).collect();// 处理没有数字的情况if digits.is_empty() {return None;}// 验证并格式化电话号码validate_and_format_with_error_handling(digits)
}fn validate_and_format_with_error_handling(digits: String) -> Option<String> {match digits.len() {10 => {validate_ten_digit_number_with_error_handling(&digits)}11 => {if digits.starts_with('1') {validate_ten_digit_number_with_error_handling(&digits[1..])} else {None}}_ => None,}
}fn validate_ten_digit_number_with_error_handling(digits: &str) -> Option<String> {let area_code = &digits[0..3];let exchange_code = &digits[3..6];// 更详细的验证if !is_valid_area_code_detailed(area_code) {return None;}if !is_valid_exchange_code_detailed(exchange_code) {return None;}Some(digits.to_string())
}fn is_valid_area_code_detailed(area_code: &str) -> bool {// 地区代码不能以0或1开头if area_code.starts_with('0') || area_code.starts_with('1') {return false;}// 额外的业务规则可以在这里添加true
}fn is_valid_exchange_code_detailed(exchange_code: &str) -> bool {// 交换代码不能以0或1开头if exchange_code.starts_with('0') || exchange_code.starts_with('1') {return false;}// 额外的业务规则可以在这里添加true
}// 返回详细错误信息的版本
#[derive(Debug, PartialEq)]
pub enum PhoneNumberError {InvalidLength,InvalidCountryCode,InvalidAreaCode,InvalidExchangeCode,NoDigits,
}impl std::fmt::Display for PhoneNumberError {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {match self {PhoneNumberError::InvalidLength => write!(f, "电话号码长度无效"),PhoneNumberError::InvalidCountryCode => write!(f, "国家代码无效"),PhoneNumberError::InvalidAreaCode => write!(f, "地区代码无效"),PhoneNumberError::InvalidExchangeCode => write!(f, "交换代码无效"),PhoneNumberError::NoDigits => write!(f, "未找到数字"),}}
}impl std::error::Error for PhoneNumberError {}pub fn number_detailed(user_number: &str) -> Result<String, PhoneNumberError> {// 处理空字符串if user_number.is_empty() {return Err(PhoneNumberError::NoDigits);}// 提取所有数字字符let digits: String = user_number.chars().filter(|c| c.is_ascii_digit()).collect();// 处理没有数字的情况if digits.is_empty() {return Err(PhoneNumberError::NoDigits);}// 验证并格式化电话号码validate_and_format_detailed(digits)
}fn validate_and_format_detailed(digits: String) -> Result<String, PhoneNumberError> {match digits.len() {10 => {validate_ten_digit_number_detailed(&digits)}11 => {if digits.starts_with('1') {validate_ten_digit_number_detailed(&digits[1..])} else {Err(PhoneNumberError::InvalidCountryCode)}}_ => Err(PhoneNumberError::InvalidLength),}
}fn validate_ten_digit_number_detailed(digits: &str) -> Result<String, PhoneNumberError> {let area_code = &digits[0..3];let exchange_code = &digits[3..6];if area_code.starts_with('0') || area_code.starts_with('1') {return Err(PhoneNumberError::InvalidAreaCode);}if exchange_code.starts_with('0') || exchange_code.starts_with('1') {return Err(PhoneNumberError::InvalidExchangeCode);}Ok(digits.to_string())
}

扩展功能

基于基础实现,我们可以添加更多功能:

pub struct PhoneNumber {digits: String,
}impl PhoneNumber {pub fn new(user_number: &str) -> Option<Self> {number(user_number).map(|digits| PhoneNumber { digits })}pub fn new_unchecked(digits: String) -> Self {PhoneNumber { digits }}pub fn area_code(&self) -> &str {&self.digits[0..3]}pub fn exchange_code(&self) -> &str {&self.digits[3..6]}pub fn number(&self) -> &str {&self.digits[6..]}pub fn full_number(&self) -> &str {&self.digits}pub fn to_formatted_string(&self) -> String {format!("({}) {}-{}", self.area_code(), self.exchange_code(), self.number())}pub fn is_valid(&self) -> bool {self.digits.len() == 10 && is_valid_area_code(self.area_code()) && is_valid_exchange_code(self.exchange_code())}
}pub fn number(user_number: &str) -> Option<String> {let digits: String = user_number.chars().filter(|c| c.is_ascii_digit()).collect();validate_and_format(digits)
}fn validate_and_format(digits: String) -> Option<String> {match digits.len() {10 => {validate_ten_digit_number(&digits)}11 => {if digits.starts_with('1') {validate_ten_digit_number(&digits[1..])} else {None}}_ => None,}
}fn validate_ten_digit_number(digits: &str) -> Option<String> {let area_code = &digits[0..3];let exchange_code = &digits[3..6];if is_valid_area_code(area_code) && is_valid_exchange_code(exchange_code) {Some(digits.to_string())} else {None}
}fn is_valid_area_code(area_code: &str) -> bool {!area_code.starts_with('0') && !area_code.starts_with('1')
}fn is_valid_exchange_code(exchange_code: &str) -> bool {!exchange_code.starts_with('0') && !exchange_code.starts_with('1')
}// 电话号码验证器
pub struct PhoneNumberValidator;impl PhoneNumberValidator {pub fn new() -> Self {PhoneNumberValidator}pub fn validate(&self, user_number: &str) -> Option<PhoneNumber> {PhoneNumber::new(user_number)}pub fn is_valid(&self, user_number: &str) -> bool {self.validate(user_number).is_some()}// 批量验证电话号码pub fn validate_batch(&self, numbers: &[&str]) -> Vec<(String, bool)> {numbers.iter().map(|&number| {let is_valid = self.is_valid(number);(number.to_string(), is_valid)}).collect()}// 查找有效的电话号码pub fn find_valid_numbers(&self, numbers: &[&str]) -> Vec<String> {numbers.iter().filter_map(|&number| self.validate(number)).map(|phone| phone.full_number().to_string()).collect()}// 格式化电话号码(如果有效)pub fn format_if_valid(&self, user_number: &str) -> Option<String> {self.validate(user_number).map(|phone| phone.to_formatted_string())}
}// 电话号码分析器
pub struct PhoneNumberAnalysis {pub original_input: String,pub cleaned_number: Option<String>,pub is_valid: bool,pub area_code: Option<String>,pub exchange_code: Option<String>,pub number_part: Option<String>,pub formatted_number: Option<String>,
}impl PhoneNumberValidator {pub fn analyze(&self, user_number: &str) -> PhoneNumberAnalysis {let cleaned_number = number(user_number);let (area_code, exchange_code, number_part, formatted_number) = if let Some(ref phone) = cleaned_number {let phone_obj = PhoneNumber::new_unchecked(phone.clone());(Some(phone_obj.area_code().to_string()),Some(phone_obj.exchange_code().to_string()),Some(phone_obj.number().to_string()),Some(phone_obj.to_formatted_string()),)} else {(None, None, None, None)};PhoneNumberAnalysis {original_input: user_number.to_string(),cleaned_number,is_valid: cleaned_number.is_some(),area_code,exchange_code,number_part,formatted_number,}}
}// 便利函数
pub fn format_phone_number(user_number: &str) -> Option<String> {let validator = PhoneNumberValidator::new();validator.format_if_valid(user_number)
}pub fn is_valid_phone_number(user_number: &str) -> bool {let validator = PhoneNumberValidator::new();validator.is_valid(user_number)
}

实际应用场景

电话号码处理在实际开发中有以下应用:

  1. 通讯应用:电话、短信、视频通话应用
  2. 电商平台:用户注册、订单联系信息
  3. 社交网络:用户资料、好友联系
  4. 金融服务:银行、支付应用的用户验证
  5. 医疗健康:预约系统、患者联系
  6. 物流配送:快递、外卖的联系信息
  7. 企业管理系统:客户关系管理、员工信息
  8. 政府服务:公共服务、政务应用

算法复杂度分析

  1. 时间复杂度:O(n)

    • 其中n是输入字符串的长度,需要遍历每个字符
  2. 空间复杂度:O(n)

    • 需要存储提取的数字字符

与其他实现方式的比较

// 使用nom解析器的实现
use nom::{character::complete::{digit1, char},combinator::{opt, map_res},sequence::{delimited, tuple},multi::many0,bytes::complete::tag,IResult,
};pub fn number_nom(user_number: &str) -> Option<String> {// 使用nom解析器库实现电话号码解析// 这里只是一个示例,实际实现会更复杂unimplemented!()
}// 使用功能完整的电话号码库实现
// [dependencies]
// phonenumber = "0.3"pub fn number_phonenumber_lib(user_number: &str) -> Option<String> {use phonenumber::Mode;match phonenumber::parse(None, user_number) {Ok(phone_number) => {if phonenumber::is_valid(&phone_number) {Some(phonenumber::format(&phone_number, Mode::E164)[1..].to_string()) // 移除+号} else {None}}Err(_) => None,}
}// 使用状态机的实现
#[derive(Debug, Clone, Copy)]
enum ParseState {Start,ReadingCountryCode,ReadingAreaCode,ReadingExchangeCode,ReadingNumber,Done,Error,
}pub fn number_state_machine(user_number: &str) -> Option<String> {let mut state = ParseState::Start;let mut digits = String::new();for c in user_number.chars() {match state {ParseState::Start => {if c.is_ascii_digit() {digits.push(c);if digits.len() == 1 && c == '1' {state = ParseState::ReadingCountryCode;} else {state = ParseState::ReadingAreaCode;}}// 忽略非数字字符}ParseState::ReadingCountryCode => {if c.is_ascii_digit() {digits.push(c);state = ParseState::ReadingAreaCode;}}ParseState::ReadingAreaCode => {if c.is_ascii_digit() {digits.push(c);if digits.len() == 3 {state = ParseState::ReadingExchangeCode;}}}ParseState::ReadingExchangeCode => {if c.is_ascii_digit() {digits.push(c);if digits.len() == 6 {state = ParseState::ReadingNumber;}}}ParseState::ReadingNumber => {if c.is_ascii_digit() {digits.push(c);if digits.len() == 10 {state = ParseState::Done;}}}ParseState::Done | ParseState::Error => {if c.is_ascii_digit() {// 超过10位数字state = ParseState::Error;}}}}if state == ParseState::Done || (state == ParseState::ReadingNumber && digits.len() == 10) {Some(digits)} else {None}
}// 使用外部API验证的实现
// [dependencies]
// reqwest = "0.11"
// tokio = { version = "1", features = ["full"] }pub async fn number_with_api_validation(user_number: &str) -> Option<String> {let cleaned_number = number(user_number)?;// 这里可以调用外部API验证电话号码是否真实存在// let client = reqwest::Client::new();// let response = client//     .post("https://api.phonenumberverification.com/validate")//     .json(&serde_json::json!({"number": cleaned_number}))//     .send()//     .await;// // if let Ok(resp) = response {//     if resp.status().is_success() {//         return Some(cleaned_number);//     }// }Some(cleaned_number) // 为示例直接返回
}

总结

通过 phone-number 练习,我们学到了:

  1. 字符串处理:掌握了从复杂字符串中提取和验证数据的技巧
  2. 正则表达式:学会了使用正则表达式进行模式匹配和数据提取
  3. 错误处理:深入理解了Option和Result类型在数据验证中的应用
  4. 业务规则实现:了解了如何将复杂的业务规则转换为代码实现
  5. 性能优化:学会了预分配内存和使用高效算法等优化技巧
  6. 数据封装:理解了如何设计结构体来封装和操作复杂数据

这些技能在实际开发中非常有用,特别是在数据处理、表单验证、用户输入处理等场景中。电话号码处理虽然是一个具体的应用问题,但它涉及到了字符串处理、正则表达式、错误处理、业务规则实现等许多核心概念,是学习Rust实用编程的良好起点。

通过这个练习,我们也看到了Rust在数据处理和验证方面的强大能力,以及如何用安全且高效的方式实现复杂的业务规则。这种结合了安全性和性能的语言特性正是Rust的魅力所在。

http://www.dtcms.com/a/585386.html

相关文章:

  • CUDA C++编程指南(3.2.5)——分布式共享内存
  • 华为路由器核心技术详解:数据包的智能导航系统
  • Go基础:字符串常用的系统函数及对应案例详解
  • redis查询速度快的原因?
  • 社区类网站开发网站怎么提升流量
  • 注册网站时手机号格式不正确容易做的html5的网站
  • 如何查询哪些服务器 IP 访问了 Google Cloud 的 Vertex AI API
  • DataWhale-HelloAgents(第一部分:智能体与语言模型基础)
  • Ollama:在本地运行大语言模型的利器
  • 构建智能知识库问答助手:LangChain与大语言模型的深度融合实践
  • 大语言模型如何获得符号逻辑演绎能力?从频率范式到贝叶斯范式的转移
  • 网站建设中的功能新浪微博图床wordpress
  • 【玩泰山派】9、ubuntu22.04安装中文输入法
  • Spring IOC/DI 与 MVC 从入门到实战
  • SCNet超算平台DCU异构环境的Ollama启动服务后无法转发公网的问题解决
  • macOS下如何全文检索epub格式文件?
  • 一键配置 macOS 终极终端:iTerm2 + Oh My Zsh 自动化安装脚本
  • 如何在 Mac、Ubuntu、CentOS、Windows 上安装 MySQL 客户端
  • 石景山广州网站建设外贸soho建站多少钱
  • 某观鸟记录中心的爬虫——mitmproxy的简单使用
  • 58同城上海网站建设北京朝阳区房价
  • 金融网络销售怎么找客源公司网站做优化少钱
  • 代码随想录 Q84.分发饼干
  • 11.8 脚本网页 打砖块max
  • 终极笔记应用程序Alexandrie
  • 「嵌」入未来,「式」界无限 · 第5篇:能源电力的智能化跃迁
  • 自动化实践(7.25):把 PsTools 接入 PowerShell / 批处理 / Ansible
  • 太原在线网站建设深圳网站关键词优化
  • AWS Lambda的安全之道:S3静态加密与运行时完整性检查的双重保障
  • 时序数据库选型指南从大数据视角看IoTDB的核心优势