Solution

  1. // This is the buggy version that appears in the problem.
  2. #[cfg(never)]
  3. pub fn luhn(cc_number: &str) -> bool {
  4.     let mut sum = 0;
  5.     let mut double = false;
  6.     for c in cc_number.chars().rev() {
  7.         if let Some(digit) = c.to_digit(10) {
  8.             if double {
  9.                 let double_digit = digit * 2;
  10.                 sum +=
  11.                     if double_digit > 9 { double_digit - 9 } else { double_digit };
  12.             } else {
  13.                 sum += digit;
  14.             }
  15.             double = !double;
  16.         } else {
  17.             continue;
  18.         }
  19.     }
  20.     sum % 10 == 0
  21. }
  22. // This is the solution and passes all of the tests below.
  23. pub fn luhn(cc_number: &str) -> bool {
  24.     let mut sum = 0;
  25.     let mut double = false;
  26.     let mut digits = 0;
  27.     for c in cc_number.chars().rev() {
  28.         if let Some(digit) = c.to_digit(10) {
  29.             digits += 1;
  30.             if double {
  31.                 let double_digit = digit * 2;
  32.                 sum +=
  33.                     if double_digit > 9 { double_digit - 9 } else { double_digit };
  34.             } else {
  35.                 sum += digit;
  36.             }
  37.             double = !double;
  38.         } else if c.is_whitespace() {
  39.             // New: accept whitespace.
  40.             continue;
  41.         } else {
  42.             // New: reject all other characters.
  43.             return false;
  44.         }
  45.     }
  46.     // New: check that we have at least two digits
  47.     digits >= 2 && sum % 10 == 0
  48. }
  49. fn main() {
  50.     let cc_number = "1234 5678 1234 5670";
  51.     println!(
  52.         "Is {cc_number} a valid credit card number? {}",
  53.         if luhn(cc_number) { "yes" } else { "no" }
  54.     );
  55. }
  56. #[cfg(test)]
  57. mod test {
  58.     use super::*;
  59.     #[test]
  60.     fn test_valid_cc_number() {
  61.         assert!(luhn("4263 9826 4026 9299"));
  62.         assert!(luhn("4539 3195 0343 6467"));
  63.         assert!(luhn("7992 7398 713"));
  64.     }
  65.     #[test]
  66.     fn test_invalid_cc_number() {
  67.         assert!(!luhn("4223 9826 4026 9299"));
  68.         assert!(!luhn("4539 3195 0343 6476"));
  69.         assert!(!luhn("8273 1232 7352 0569"));
  70.     }
  71.     #[test]
  72.     fn test_non_digit_cc_number() {
  73.         assert!(!luhn("foo"));
  74.         assert!(!luhn("foo 0 0"));
  75.     }
  76.     #[test]
  77.     fn test_empty_cc_number() {
  78.         assert!(!luhn(""));
  79.         assert!(!luhn(" "));
  80.         assert!(!luhn("  "));
  81.         assert!(!luhn("    "));
  82.     }
  83.     #[test]
  84.     fn test_single_digit_cc_number() {
  85.         assert!(!luhn("0"));
  86.     }
  87.     #[test]
  88.     fn test_two_digit_cc_number() {
  89.         assert!(luhn(" 0 0 "));
  90.     }
  91. }