Rust 练习册 :Luhn与校验算法

在现代金融和数据处理系统中,确保数据的准确性至关重要。Luhn算法(也称为模10算法)是一种简单的校验和算法,广泛用于验证各种识别号码,如信用卡号、IMEI号码等。在 Exercism 的 "luhn" 练习中,我们需要实现Luhn算法来验证给定的字符串是否符合校验规则。这不仅能帮助我们掌握字符串处理和数学计算技巧,还能深入学习Rust中的字符处理、错误处理和算法实现。

什么是Luhn算法?

Luhn算法是由IBM科学家Hans Peter Luhn在1954年发明的一种校验和算法,用于验证各种识别号码的准确性。它主要用于信用卡号、IMEI号码、加拿大社会保险号等。

Luhn算法的工作原理如下:

  1. 从右到左,对偶数位置的数字乘以2(第二个、第四个、第六个数字等)
  2. 如果乘以2的结果大于9,则将结果的各位数字相加(或减去9)
  3. 将所有数字相加
  4. 如果总和能被10整除,则该号码有效

例如,验证号码"4539 3195 0343 6467":

  1. 移除空格得到"4539319503436467"
  2. 从右到左,对偶数位置的数字乘以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
  3. 大于9的数字减去9:
    • 7 3 4 6 4 0 5 9 1 6 9 1 3 8 5 8
  4. 求和:7+3+4+6+4+0+5+9+1+6+9+1+3+8+5+8 = 90
  5. 90能被10整除,因此号码有效

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

rust 复制代码
/// Check a Luhn checksum.
pub fn is_valid(code: &str) -> bool {
    unimplemented!("Is the Luhn checksum for {} valid?", code);
}

我们需要实现一个函数,验证给定的字符串是否符合Luhn校验规则。

设计分析

1. 核心要求

  1. 字符处理:处理数字和空格字符
  2. 位置计算:从右到左计算数字位置
  3. 数学运算:执行乘法和加法运算
  4. 校验和验证:验证总和是否能被10整除
  5. 输入验证:处理无效字符和格式

2. 技术要点

  1. 字符串处理:过滤和转换字符
  2. 逆序处理:从右到左处理数字
  3. 数学计算:执行校验和计算
  4. 边界情况:处理各种无效输入

完整实现

1. 基础实现

rust 复制代码
/// 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() {
        // 从右到左,偶数位置的数字需要乘以2
        if (len - i) % 2 == 0 {
            let doubled = digit * 2;
            sum += if doubled > 9 { doubled - 9 } else { doubled };
        } else {
            sum += digit;
        }
    }
    
    sum % 10 == 0
}

2. 优化实现

rust 复制代码
/// 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. 高性能实现

rust 复制代码
/// 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
}

测试用例分析

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

rust 复制代码
#[test]
fn test_single_digit_strings_can_not_be_valid() {
    process_valid_case("1", false);
}

单个数字不能通过验证。

rust 复制代码
#[test]
fn test_a_single_zero_is_invalid() {
    process_valid_case("0", false);
}

单个零也不能通过验证。

rust 复制代码
#[test]
fn test_a_simple_valid_sin_that_remains_valid_if_reversed() {
    process_valid_case("059", true);
}

有效的简单号码,即使反转也有效。

rust 复制代码
#[test]
fn test_a_valid_canadian_sin() {
    process_valid_case("055 444 285", true);
}

有效的加拿大社会保险号。

rust 复制代码
#[test]
fn test_invalid_credit_card() {
    process_valid_case("8273 1232 7352 0569", false);
}

无效的信用卡号。

rust 复制代码
#[test]
fn strings_that_contain_non_digits_are_invalid() {
    process_valid_case("055a 444 285", false);
}

包含非数字字符的字符串无效。

rust 复制代码
#[test]
fn test_more_than_a_single_zero_is_valid() {
    process_valid_case("0000 0", true);
}

多个零可以是有效的。

rust 复制代码
#[test]
fn test_using_ascii_value_for_doubled_nondigit_isnt_allowed() {
    process_valid_case(":9", false);
}

不能使用非数字字符的ASCII值进行计算。

性能优化版本

考虑性能的优化实现:

rust 复制代码
/// 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
}

错误处理和边界情况

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

rust 复制代码
#[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
}

扩展功能

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

rust 复制代码
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算法在实际开发中有以下应用:

  1. 支付系统:验证信用卡号和借记卡号
  2. 移动设备:验证IMEI号码
  3. 身份验证:验证各种身份证件号码
  4. 数据清洗:检测和纠正输入错误
  5. 表单验证:在用户输入时实时验证
  6. 库存管理:验证产品序列号
  7. 银行系统:验证账户号码
  8. 电商平台:验证支付信息

算法复杂度分析

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

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

    • 需要存储过滤后的数字数组

与其他实现方式的比较

rust 复制代码
// 使用函数式编程的实现
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 练习,我们学到了:

  1. 字符串处理:掌握了字符过滤、转换和验证技巧
  2. 算法实现:学会了实现经典的校验和算法
  3. 数学计算:熟练使用模运算和数字处理
  4. 错误处理:深入理解了Result类型处理错误情况
  5. 性能优化:了解了不同实现方式的性能特点
  6. 边界情况处理:学会了处理各种输入边界情况

这些技能在实际开发中非常有用,特别是在处理数据验证、表单处理、支付系统等场景中。Luhn算法虽然是一个具体的校验算法,但它涉及到了字符串处理、算法实现、错误处理等许多核心概念,是学习Rust实用编程的良好起点。

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

相关推荐
代码雕刻家2 小时前
C语言中关于类型转换不匹配的解决方案
c语言·开发语言·算法
程序猿_极客2 小时前
【2025】16届蓝桥杯 Java 组全题详解(省赛真题 + 思路 + 代码)
java·开发语言·职场和发展·蓝桥杯
毕设源码-邱学长2 小时前
【开题答辩全过程】以 “万家电器”仓库管理系统的设计与实现为例,包含答辩的问题和答案
java·eclipse
摇滚侠2 小时前
Spring Boot3零基础教程,响应式编程的模型,笔记109
java·spring boot·笔记
星星的月亮叫太阳2 小时前
large-scale-DRL-exploration 代码阅读 总结
python·算法
wfsm3 小时前
flowable使用01
java·前端·servlet
员大头硬花生3 小时前
七、InnoDB引擎-架构-后台线程
java·数据库·mysql
王哈哈^_^3 小时前
YOLOv11视觉检测实战:安全距离测算全解析
人工智能·数码相机·算法·yolo·计算机视觉·目标跟踪·视觉检测
..Cherry..3 小时前
Etcd详解(raft算法保证强一致性)
数据库·算法·etcd