如何使用Rust迭代器, 以Luhn Algorithm的实现为例

发布时间 2023-03-23 17:39:40作者: Echiduna

本题来源自谷歌的Comprehensive-Rust课程第二天下午的练习

问题描述

在这个练习的描述中, Luhn Algorithm是一种用来验证银行卡号是否合法的算法. 他的具体流程如下:

  1. 去掉输入中所有的空格
  2. 得到的数字串长度如果小于0, 说明这个字符串不合法
  3. 从数字的右侧的第二个数开始, 每隔一个数进行一次这样的处理: 将这个数字乘以2, 然后将得到的数字拆分相加. 例如, 数字7就应该首先乘以2变为14, 然后在变为1+4, 最后得到5
  4. 将处理完之后的数字全部相加, 若个位为0, 则说明输入的字符串是一个合法的银行卡号

思路分析

这道题目的思路并不难, 然而, 如何尽可能地利用Rust std中提供的API, 写出相较优美的解法, 就需要一定的思考了. 本题的核心就是遍历字符串, 并且在这个过程中, 对每个字符做出几种模式的处理. 如果全部使用for loop, 整个算法的代码会变得异常地冗杂, 因此, 对于这种情况, Rust的Iterator Trait提供了相当多的迭代器适配器(Adapter)来帮助我们用尽可能少的代码, 声明式地解决问题.

解法与分析

因为要对字符进行遍历处理, chars迭代器就必不可少了. 随后是剔除空格, 使用filter迭代器可以将上一步得到的字符迭代器进行进一步筛选, 得到其中非空格的内容. 由于题目要求从右到左处理字符串, 因此使用rev迭代器将字符串反转. 所有的奇数索引的数字, 都需要进行特殊的运算, 因此使用enumerate迭代器为上一轮的迭代器增加一个表示索引的维度. 到这里, 准备工作都已经完成, 我们就可以进行字符串转化为数字的操作了. 使用(to_digit)[https://doc.rust-lang.org/std/primitive.char.html#method.to_digit] 并指定转化字符为对应的十进制数的同时, 因为to_digit会对不可转化为数字的字符返回None, 所以我们可以使用let Some() 直接匹配出字符中的数字. 在这里, for_each迭代器并不是必要的, 也可以使用for loop来代替. 每次匹配出数字的同时, 更新统计数字个数和统计总和的变量即可.

  
pub fn luhn(cc_number: &str) -> bool {  
    let mut digits_seen = 0;  
    let mut sum = 0;  
    cc_number.chars().rev().filter(|&ch| ch != ' ').enumerate()  
        .for_each(  
            |(i, ch)| {  
                if let Some(d) = ch.to_digit(10) {  
                    sum += if i % 2 == 1 {  
                        let dd = d * 2;  
                        dd / 10 + dd % 10 } else { d };  
                    digits_seen += 1; }  
            });  
    if digits_seen < 2 {  
        return false;  
    }  
    sum % 10 == 0  
}  
  
  
#[cfg(test)]  
mod tests {  
    use super::*;  
  
    #[test]  
    fn test_non_digit_cc_number() {  
        assert!(!luhn("foo"));  
    }  
  
    #[test]  
    fn test_empty_cc_number() {  
        assert!(!luhn(""));  
        assert!(!luhn(" "));  
        assert!(!luhn("  "));  
        assert!(!luhn("    "));  
    }  
  
    #[test]  
    fn test_single_digit_cc_number() {  
        assert!(!luhn("0"));  
    }  
  
    #[test]  
    fn test_two_digit_cc_number() {  
        assert!(luhn(" 0 0 "));  
    }  
  
    #[test]  
    fn test_valid_cc_number() {  
        assert!(luhn("4263 9826 4026 9299"));  
        assert!(luhn("4539 3195 0343 6467"));  
        assert!(luhn("7992 7398 713"));  
    }  
  
    #[test]  
    fn test_invalid_cc_number() {  
        assert!(!luhn("4223 9826 4026 9299"));  
        assert!(!luhn("4539 3195 0343 6476"));  
        assert!(!luhn("8273 1232 7352 0569"));  
    }  
}