Rust 练习册 :Luhn Trait与Trait实现

在Rust中,trait是定义共享行为的主要方式,类似于其他语言中的接口。通过trait,我们可以为不同类型实现相同的方法,实现代码的复用和抽象。在 Exercism 的 "luhn-trait" 练习中,我们需要为Luhn trait实现多种类型的valid_luhn方法。这不仅能帮助我们掌握Rust的trait系统,还能深入学习如何设计灵活且可扩展的API。

什么是Trait?

Trait是Rust中定义共享行为的方式,它声明了某些类型必须提供的方法集合。Trait类似于其他语言中的接口,但更加强大和灵活。通过实现trait,类型可以共享相同的行为,这使得我们可以编写更加通用和可复用的代码。

在我们的练习中,需要为Luhn trait实现valid_luhn方法,使其能够为多种类型(如&str、String、各种整数类型)提供Luhn校验和验证功能。

让我们先看看练习提供的trait定义和示例实现:

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

/// Here is the example of how to implement custom Luhn trait
/// for the &str type. Naturally, you can implement this trait
/// by hand for the every other type presented in the test suite,
/// but your solution will fail if a new type is presented.
/// Perhaps there exists a better solution for this problem?
impl<'a> Luhn for &'a str {
    fn valid_luhn(&self) -> bool {
        unimplemented!("Determine if '{}' is a valid credit card number.", self);
    }
}

我们需要为Luhn trait实现valid_luhn方法,使其能够为多种类型提供Luhn校验功能。

设计分析

1. 核心要求

  1. Trait定义:定义Luhn trait及valid_luhn方法
  2. 类型实现:为多种类型实现Luhn trait
  3. 算法实现:实现Luhn校验和验证算法
  4. API设计:设计简洁且易于使用的API

2. 技术要点

  1. 泛型实现:使用泛型和trait约束减少重复代码
  2. 字符串处理:处理各种类型的输入数据
  3. 生命周期管理:正确处理引用的生命周期
  4. 代码复用:避免为每种类型重复实现相同逻辑

完整实现

1. 基础实现

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        // 移除空格并验证字符
        let cleaned: String = self.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
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

impl Luhn for u8 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u16 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u32 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u64 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for usize {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

2. 使用泛型优化的实现

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

// 为str实现Luhn trait
impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u32> = self
            .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
    }
}

// 为String实现Luhn trait
impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

// 为数字类型实现Luhn trait
impl Luhn for u8 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u16 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u32 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for u64 {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

impl Luhn for usize {
    fn valid_luhn(&self) -> bool {
        self.to_string().valid_luhn()
    }
}

3. 使用宏减少重复代码的实现

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u32> = self
            .chars()
            .filter(|c| !c.is_whitespace())
            .map(|c| c.to_digit(10))
            .collect::<Option<Vec<u32>>>()
            .unwrap_or_default();
        
        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
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

// 使用宏为数字类型实现Luhn trait
macro_rules! impl_luhn_for_numbers {
    ($($t:ty),*) => {
        $(
            impl Luhn for $t {
                fn valid_luhn(&self) -> bool {
                    self.to_string().valid_luhn()
                }
            }
        )*
    };
}

impl_luhn_for_numbers!(u8, u16, u32, u64, usize);

测试用例分析

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

rust 复制代码
#[test]
fn you_can_validate_from_a_str() {
    assert!("046 454 286".valid_luhn());
    assert!(!"046 454 287".valid_luhn());
}

可以直接在&str上调用valid_luhn方法进行验证。

rust 复制代码
#[test]
fn you_can_validate_from_a_string() {
    assert!(String::from("046 454 286").valid_luhn());
    assert!(!String::from("046 454 287").valid_luhn());
}

可以在String上调用valid_luhn方法进行验证。

rust 复制代码
#[test]
fn you_can_validate_from_a_u8() {
    assert!(240u8.valid_luhn());
    assert!(!241u8.valid_luhn());
}

可以在u8上调用valid_luhn方法进行验证。

rust 复制代码
#[test]
fn you_can_validate_from_a_u16() {
    let valid = 64_436u16;
    let invalid = 64_437u16;
    assert!(valid.valid_luhn());
    assert!(!invalid.valid_luhn());
}

可以在u16上调用valid_luhn方法进行验证。

性能优化版本

考虑性能的优化实现:

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        let mut digits = Vec::with_capacity(self.len());
        
        // 预分配并验证字符
        for c in self.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
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

// 使用宏为数字类型实现Luhn trait
macro_rules! impl_luhn_for_numbers {
    ($($t:ty),*) => {
        $(
            impl Luhn for $t {
                fn valid_luhn(&self) -> bool {
                    self.to_string().valid_luhn()
                }
            }
        )*
    };
}

impl_luhn_for_numbers!(u8, u16, u32, u64, usize);

错误处理和边界情况

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

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

#[derive(Debug, PartialEq)]
pub enum LuhnError {
    TooShort,
    InvalidCharacter(char),
    EmptyInput,
}

impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        self.validate_luhn().unwrap_or(false)
    }
}

impl str {
    pub fn validate_luhn(&self) -> Result<bool, LuhnError> {
        if self.is_empty() {
            return Err(LuhnError::EmptyInput);
        }
        
        let mut digits = Vec::new();
        
        for c in self.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)
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

// 使用宏为数字类型实现Luhn trait
macro_rules! impl_luhn_for_numbers {
    ($($t:ty),*) => {
        $(
            impl Luhn for $t {
                fn valid_luhn(&self) -> bool {
                    self.to_string().valid_luhn()
                }
            }
        )*
    };
}

impl_luhn_for_numbers!(u8, u16, u32, u64, usize);

扩展功能

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

rust 复制代码
pub trait Luhn {
    fn valid_luhn(&self) -> bool;
    
    // 扩展方法:获取清理后的数字
    fn clean_digits(&self) -> Result<Vec<u32>, LuhnError>
    where
        Self: Sized,
    {
        let s = self.to_string();
        s.as_str().clean_digits()
    }
    
    // 扩展方法:生成校验位
    fn generate_check_digit(&self) -> Option<u32>
    where
        Self: Sized,
    {
        let s = self.to_string();
        s.as_str().generate_check_digit()
    }
}

pub trait LuhnExt: Luhn {
    fn clean_digits(&self) -> Result<Vec<u32>, LuhnError>;
    fn generate_check_digit(&self) -> Option<u32>;
}

impl Luhn for str {
    fn valid_luhn(&self) -> bool {
        self.validate_luhn().unwrap_or(false)
    }
}

impl LuhnExt for str {
    fn clean_digits(&self) -> Result<Vec<u32>, LuhnError> {
        if self.is_empty() {
            return Err(LuhnError::EmptyInput);
        }
        
        let mut digits = Vec::new();
        
        for c in self.chars() {
            if c.is_whitespace() {
                continue;
            }
            
            match c.to_digit(10) {
                Some(digit) => digits.push(digit),
                None => return Err(LuhnError::InvalidCharacter(c)),
            }
        }
        
        Ok(digits)
    }
    
    fn generate_check_digit(&self) -> Option<u32> {
        let digits = self.clean_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)
    }
}

impl str {
    pub fn validate_luhn(&self) -> Result<bool, LuhnError> {
        if self.is_empty() {
            return Err(LuhnError::EmptyInput);
        }
        
        let mut digits = Vec::new();
        
        for c in self.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)
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        self.as_str().valid_luhn()
    }
}

// 使用宏为数字类型实现Luhn trait
macro_rules! impl_luhn_for_numbers {
    ($($t:ty),*) => {
        $(
            impl Luhn for $t {
                fn valid_luhn(&self) -> bool {
                    self.to_string().valid_luhn()
                }
            }
        )*
    };
}

impl_luhn_for_numbers!(u8, u16, u32, u64, usize);

#[derive(Debug, PartialEq)]
pub enum LuhnError {
    TooShort,
    InvalidCharacter(char),
    EmptyInput,
}

实际应用场景

Trait和Luhn算法在实际开发中有以下应用:

  1. 支付系统:验证信用卡号和借记卡号
  2. API设计:创建灵活且易于使用的API
  3. 数据验证:验证各种识别号码的准确性
  4. 表单处理:处理用户输入的不同数据类型
  5. 库开发:设计可扩展的库接口
  6. 类型安全:确保类型转换的安全性
  7. 移动设备:验证IMEI号码
  8. 身份验证:验证各种身份证件号码

算法复杂度分析

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

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

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

与其他实现方式的比较

rust 复制代码
// 使用默认实现的trait
pub trait Luhn {
    fn valid_luhn(&self) -> bool {
        let s = self.to_string();
        // Luhn算法实现
        let digits: Vec<u32> = s
            .chars()
            .filter(|c| !c.is_whitespace())
            .map(|c| c.to_digit(10))
            .collect::<Option<Vec<u32>>>()
            .unwrap_or_default();
        
        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
    }
}

impl Luhn for str {}
impl Luhn for String {}
impl Luhn for u8 {}
impl Luhn for u16 {}
impl Luhn for u32 {}
impl Luhn for u64 {}
impl Luhn for usize {}

// 使用关联类型的trait
pub trait Luhn {
    type Error;
    
    fn valid_luhn(&self) -> Result<bool, Self::Error>;
}

impl Luhn for str {
    type Error = LuhnError;
    
    fn valid_luhn(&self) -> Result<bool, Self::Error> {
        // 实现...
        Ok(true)
    }
}

// 使用泛型trait
pub trait Luhn<T> {
    fn valid_luhn(&self) -> bool;
}

impl Luhn<&str> for &str {
    fn valid_luhn(&self) -> bool {
        // 实现...
        true
    }
}

// 使用继承trait
pub trait Validate {
    fn validate(&self) -> bool;
}

pub trait Luhn: Validate {
    fn valid_luhn(&self) -> bool {
        self.validate()
    }
}

impl Validate for str {
    fn validate(&self) -> bool {
        // 实现...
        true
    }
}

impl Luhn for str {}

总结

通过 luhn-trait 练习,我们学到了:

  1. Trait系统:掌握了Rust中trait的定义和实现
  2. 泛型编程:学会了使用泛型减少重复代码
  3. 宏系统:了解了如何使用宏简化重复实现
  4. API设计:学会了设计灵活且易于使用的API
  5. 类型系统:深入理解了Rust的类型系统和trait系统
  6. 代码复用:学会了如何避免重复代码并提高可维护性

这些技能在实际开发中非常有用,特别是在设计库API、处理类型转换、构建可扩展系统等场景中。Trait虽然是Rust类型系统的核心概念,但它体现了Rust设计哲学中组合优于继承的思想,是学习Rust高级特性的良好起点。

通过这个练习,我们也看到了Rust在trait系统和API设计方面的强大能力,以及如何用安全且灵活的方式实现可扩展的类型系统。这种结合了安全性和灵活性的语言特性正是Rust的魅力所在。

相关推荐
爱编程的鱼1 小时前
ESLint 是什么?
开发语言·网络·人工智能·网络协议
njnu@liyong2 小时前
HTTP-大文件传输处理
网络·网络协议·http
ゞ 正在缓冲99%…2 小时前
leetcode1770.执行乘法运算的最大分数
java·数据结构·算法·动态规划
abcefg_h2 小时前
链表算法---基本算法操作(go语言版)
算法·链表·golang
小O的算法实验室2 小时前
2022年IEEE TITS SCI2区TOP,基于切线交点和目标引导策略的无人机自主路径规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
Mr_Oak2 小时前
【multi-model】moco系列&SimCLR&BEiT
人工智能·深度学习·神经网络·算法·计算机视觉·transformer·对比学习
尼古拉斯·纯情暖男·天真·阿玮3 小时前
动态规划——子序列问题
java·算法·动态规划
Mr_Xuhhh3 小时前
应用层协议HTTP(1)
网络·网络协议·http
..空空的人3 小时前
C++基于websocket的多用户网页五子棋 --- 认识依赖库
网络·websocket·网络协议