Rust 练习册 :Luhn与校验算法
在现代金融和数据处理系统中,确保数据的准确性至关重要。Luhn算法(也称为模10算法)是一种简单的校验和算法,广泛用于验证各种识别号码,如信用卡号、IMEI号码等。在 Exercism 的 “luhn” 练习中,我们需要实现Luhn算法来验证给定的字符串是否符合校验规则。这不仅能帮助我们掌握字符串处理和数学计算技巧,还能深入学习Rust中的字符处理、错误处理和算法实现。
什么是Luhn算法?
Luhn算法是由IBM科学家Hans Peter Luhn在1954年发明的一种校验和算法,用于验证各种识别号码的准确性。它主要用于信用卡号、IMEI号码、加拿大社会保险号等。
Luhn算法的工作原理如下:
- 从右到左,对偶数位置的数字乘以2(第二个、第四个、第六个数字等)
- 如果乘以2的结果大于9,则将结果的各位数字相加(或减去9)
- 将所有数字相加
- 如果总和能被10整除,则该号码有效
例如,验证号码"4539 3195 0343 6467":
- 移除空格得到"4539319503436467"
- 从右到左,对偶数位置的数字乘以2:
- 7 6 4 3 4 0 5 9 1 3 9 5 3 4 5 4
- 7 12 4 6 4 0 5 18 1 6 9 10 3 8 5 8
- 大于9的数字减去9:
- 7 3 4 6 4 0 5 9 1 6 9 1 3 8 5 8
- 求和:7+3+4+6+4+0+5+9+1+6+9+1+3+8+5+8 = 90
- 90能被10整除,因此号码有效
让我们先看看练习提供的函数签名:
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {unimplemented!("Is the Luhn checksum for {} valid?", code);
}
我们需要实现一个函数,验证给定的字符串是否符合Luhn校验规则。
设计分析
1. 核心要求
- 字符处理:处理数字和空格字符
- 位置计算:从右到左计算数字位置
- 数学运算:执行乘法和加法运算
- 校验和验证:验证总和是否能被10整除
- 输入验证:处理无效字符和格式
2. 技术要点
- 字符串处理:过滤和转换字符
- 逆序处理:从右到左处理数字
- 数学计算:执行校验和计算
- 边界情况:处理各种无效输入
完整实现
1. 基础实现
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {// 移除空格并验证字符let cleaned: String = code.chars().filter(|c| !c.is_whitespace()).collect();// 检查长度和字符有效性if cleaned.len() <= 1 || !cleaned.chars().all(|c| c.is_ascii_digit()) {return false;}let digits: Vec<u32> = cleaned.chars().map(|c| c.to_digit(10).unwrap()).collect();let mut sum = 0;let len = digits.len();for (i, &digit) in digits.iter().enumerate() {// 从右到左,偶数位置的数字需要乘以2if (len - i) % 2 == 0 {let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0
}
2. 优化实现
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {let digits: Vec<u32> = code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect::<Option<Vec<u32>>>().unwrap_or_default();// 必须至少有2位数字if digits.len() <= 1 {return false;}let sum: u32 = digits.iter().rev() // 从右到左处理.enumerate().map(|(i, &digit)| {if i % 2 == 1 { // 偶数位置(从右数第2、4、6...位)let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();sum % 10 == 0
}
3. 高性能实现
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {let mut digits = Vec::new();// 验证并收集数字for c in code.chars() {if c.is_whitespace() {continue;}if let Some(digit) = c.to_digit(10) {digits.push(digit);} else {return false; // 包含非数字字符}}// 至少需要2位数字if digits.len() <= 1 {return false;}let mut sum = 0u32;// 从右到左处理for (i, &digit) in digits.iter().rev().enumerate() {if i % 2 == 1 { // 偶数位置(从右数第2、4、6...位)let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_single_digit_strings_can_not_be_valid() {process_valid_case("1", false);
}
单个数字不能通过验证。
#[test]
fn test_a_single_zero_is_invalid() {process_valid_case("0", false);
}
单个零也不能通过验证。
#[test]
fn test_a_simple_valid_sin_that_remains_valid_if_reversed() {process_valid_case("059", true);
}
有效的简单号码,即使反转也有效。
#[test]
fn test_a_valid_canadian_sin() {process_valid_case("055 444 285", true);
}
有效的加拿大社会保险号。
#[test]
fn test_invalid_credit_card() {process_valid_case("8273 1232 7352 0569", false);
}
无效的信用卡号。
#[test]
fn strings_that_contain_non_digits_are_invalid() {process_valid_case("055a 444 285", false);
}
包含非数字字符的字符串无效。
#[test]
fn test_more_than_a_single_zero_is_valid() {process_valid_case("0000 0", true);
}
多个零可以是有效的。
#[test]
fn test_using_ascii_value_for_doubled_nondigit_isnt_allowed() {process_valid_case(":9", false);
}
不能使用非数字字符的ASCII值进行计算。
性能优化版本
考虑性能的优化实现:
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {let mut digits = Vec::with_capacity(code.len());// 预分配并验证字符for c in code.chars() {match c {' ' => continue, // 跳过空格'0'..='9' => digits.push(c as u32 - '0' as u32),_ => return false, // 非数字字符}}// 至少需要2位数字if digits.len() <= 1 {return false;}let mut sum = 0u32;// 从右到左处理,使用索引计算位置for (i, &digit) in digits.iter().rev().enumerate() {if i % 2 == 1 { // 偶数位置(从右数第2、4、6...位)let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0
}// 使用字节数组的高性能版本
pub fn is_valid_bytes(code: &str) -> bool {let bytes = code.as_bytes();let mut digits = Vec::with_capacity(bytes.len());for &byte in bytes {match byte {b' ' => continue,b'0'..=b'9' => digits.push((byte - b'0') as u32),_ => return false,}}if digits.len() <= 1 {return false;}let mut sum = 0u32;for (i, &digit) in digits.iter().rev().enumerate() {if i % 2 == 1 {let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0
}
错误处理和边界情况
考虑更多边界情况的实现:
#[derive(Debug, PartialEq)]
pub enum LuhnError {TooShort,InvalidCharacter(char),EmptyInput,
}pub fn validate_luhn_detailed(code: &str) -> Result<bool, LuhnError> {if code.is_empty() {return Err(LuhnError::EmptyInput);}let mut digits = Vec::new();for c in code.chars() {if c.is_whitespace() {continue;}match c.to_digit(10) {Some(digit) => digits.push(digit),None => return Err(LuhnError::InvalidCharacter(c)),}}if digits.len() <= 1 {return Err(LuhnError::TooShort);}let sum: u32 = digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();Ok(sum % 10 == 0)
}/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {validate_luhn_detailed(code).unwrap_or(false)
}// 支持自定义起始位置的版本
pub fn is_valid_custom_start(code: &str, start_from_right: bool) -> bool {let digits: Result<Vec<u32>, _> = code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect();let digits = match digits {Ok(d) if d.len() > 1 => d,_ => return false,};let sum: u32 = if start_from_right {digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum()} else {digits.iter().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum()};sum % 10 == 0
}
扩展功能
基于基础实现,我们可以添加更多功能:
pub struct LuhnValidator;impl LuhnValidator {pub fn new() -> Self {LuhnValidator}/// 验证Luhn校验和pub fn is_valid(&self, code: &str) -> bool {self.validate(code).is_ok()}/// 详细验证并返回结果pub fn validate(&self, code: &str) -> Result<LuhnValidationResult, LuhnError> {if code.is_empty() {return Err(LuhnError::EmptyInput);}let mut digits = Vec::new();let mut original_chars = Vec::new();for c in code.chars() {original_chars.push(c);if c.is_whitespace() {continue;}match c.to_digit(10) {Some(digit) => digits.push(digit),None => return Err(LuhnError::InvalidCharacter(c)),}}if digits.len() <= 1 {return Err(LuhnError::TooShort);}let sum: u32 = digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();let is_valid = sum % 10 == 0;Ok(LuhnValidationResult {is_valid,digit_count: digits.len(),checksum: sum,original_input: code.to_string(),})}/// 生成符合Luhn算法的校验位pub fn generate_check_digit(&self, partial_code: &str) -> Option<u32> {let digits: Result<Vec<u32>, _> = partial_code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect();let digits = digits.ok()?;// 添加一个占位符作为校验位let mut extended_digits = digits.clone();extended_digits.push(0);let sum: u32 = extended_digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();let check_digit = (10 - (sum % 10)) % 10;Some(check_digit)}/// 为部分代码添加校验位pub fn append_check_digit(&self, partial_code: &str) -> Option<String> {let check_digit = self.generate_check_digit(partial_code)?;Some(format!("{}{}", partial_code, check_digit))}/// 批量验证多个代码pub fn validate_batch(&self, codes: &[&str]) -> Vec<(String, bool)> {codes.iter().map(|&code| (code.to_string(), self.is_valid(code))).collect()}
}#[derive(Debug)]
pub struct LuhnValidationResult {pub is_valid: bool,pub digit_count: usize,pub checksum: u32,pub original_input: String,
}#[derive(Debug, PartialEq)]
pub enum LuhnError {TooShort,InvalidCharacter(char),EmptyInput,
}// 便利函数
pub fn is_valid(code: &str) -> bool {LuhnValidator::new().is_valid(code)
}
实际应用场景
Luhn算法在实际开发中有以下应用:
- 支付系统:验证信用卡号和借记卡号
- 移动设备:验证IMEI号码
- 身份验证:验证各种身份证件号码
- 数据清洗:检测和纠正输入错误
- 表单验证:在用户输入时实时验证
- 库存管理:验证产品序列号
- 银行系统:验证账户号码
- 电商平台:验证支付信息
算法复杂度分析
-
时间复杂度:O(n)
- 需要遍历字符串中的每个字符,其中n是字符串长度
-
空间复杂度:O(n)
- 需要存储过滤后的数字数组
与其他实现方式的比较
// 使用函数式编程的实现
pub fn is_valid_functional(code: &str) -> bool {let digits: Option<Vec<u32>> = code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect();let digits = match digits {Some(d) if d.len() > 1 => d,_ => return false,};let sum: u32 = digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();sum % 10 == 0
}// 使用递归的实现
pub fn is_valid_recursive(code: &str) -> bool {fn validate_recursive(chars: &[char], digits: &mut Vec<u32>) -> bool {for &c in chars {if !c.is_whitespace() {match c.to_digit(10) {Some(digit) => digits.push(digit),None => return false,}}}if digits.len() <= 1 {return false;}let sum: u32 = digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();sum % 10 == 0}let chars: Vec<char> = code.chars().collect();let mut digits = Vec::new();validate_recursive(&chars, &mut digits)
}// 使用正则表达式的实现
use regex::Regex;pub fn is_valid_regex(code: &str) -> bool {let re = Regex::new(r"[^\d\s]").unwrap();if re.is_match(code) {return false;}let digits: Vec<u32> = code.chars().filter(|c| c.is_ascii_digit()).map(|c| c.to_digit(10).unwrap()).collect();if digits.len() <= 1 {return false;}let sum: u32 = digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();sum % 10 == 0
}// 使用迭代器链的实现
pub fn is_valid_iterator(code: &str) -> bool {code.chars().filter(|c| !c.is_whitespace()).try_fold((Vec::new(), true), |(mut digits, valid), c| {if !valid {return Ok((digits, false));}match c.to_digit(10) {Some(digit) => {digits.push(digit);Ok((digits, true))}None => Ok((digits, false))}}).map(|(digits, valid)| {if !valid || digits.len() <= 1 {return false;}digits.iter().rev().enumerate().map(|(i, &digit)| {if i % 2 == 1 {let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum::<u32>() % 10 == 0}).unwrap_or(false)
}
总结
通过 luhn 练习,我们学到了:
- 字符串处理:掌握了字符过滤、转换和验证技巧
- 算法实现:学会了实现经典的校验和算法
- 数学计算:熟练使用模运算和数字处理
- 错误处理:深入理解了Result类型处理错误情况
- 性能优化:了解了不同实现方式的性能特点
- 边界情况处理:学会了处理各种输入边界情况
这些技能在实际开发中非常有用,特别是在处理数据验证、表单处理、支付系统等场景中。Luhn算法虽然是一个具体的校验算法,但它涉及到了字符串处理、算法实现、错误处理等许多核心概念,是学习Rust实用编程的良好起点。
通过这个练习,我们也看到了Rust在数据验证和算法实现方面的强大能力,以及如何用安全且高效的方式实现经典算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。
