use digits_iterator::DigitsExtension;
pub fn valid(pan: &str) -> bool {
let mut numbers = string_to_ints(pan);
numbers.reverse();
let mut is_odd: bool = true;
let mut odd_sum: u32 = 0;
let mut even_sum: u32 = 0;
for digit in numbers {
if is_odd {
odd_sum += digit;
} else {
even_sum += digit / 5 + (2 * digit) % 10;
}
is_odd = !is_odd
}
(odd_sum + even_sum) % 10 == 0
}
fn string_to_ints(string: &str) -> Vec<u32> {
let mut numbers = vec![];
for c in string.chars() {
let value = c.to_string().parse::<u32>();
match value {
Ok(v) => numbers.push(v),
Err(e) => println!("error parsing number: {:?}", e),
}
}
numbers
}
pub fn checksum(input: &[u8]) -> u8 {
fn encode_char(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'A'..=b'Z' => c - b'A' + 10,
_ => panic!("Not alphanumeric: {}", c),
}
}
let mut ds = Vec::<u8>::with_capacity(input.len() * 2);
ds.extend(
input
.iter()
.copied()
.map(encode_char)
.flat_map(DigitsExtension::digits),
);
let checksum_even = ds
.iter()
.rev()
.skip(1)
.step_by(2)
.copied()
.flat_map(DigitsExtension::digits)
.sum::<u8>();
let checksum_odd = ds
.iter()
.rev()
.step_by(2)
.map(|&x| x * 2)
.flat_map(DigitsExtension::digits)
.sum::<u8>();
let checksum = checksum_even + checksum_odd;
let digit = (10 - (checksum % 10)) % 10;
digit + 48
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_4111111111111111() {
assert!(valid("4111111111111111"));
}
#[test]
fn accepts_49927398716() {
assert!(valid("49927398716"));
}
#[test]
fn rejects_4111111111111112() {
assert!(!valid("4111111111111112"));
}
#[test]
fn rejects_234() {
assert!(!valid("234"));
}
fn validate_isin(xs: [u8; 12]) -> bool {
let digit = checksum(&xs[0..11]);
digit == xs[11]
}
#[test]
fn validate_some_good_isins() {
assert!(validate_isin(*b"US5949181045")); assert!(validate_isin(*b"US38259P5089")); assert!(validate_isin(*b"US0378331005")); assert!(validate_isin(*b"BMG491BT1088")); assert!(validate_isin(*b"IE00B4BNMY34")); assert!(validate_isin(*b"US0231351067")); assert!(validate_isin(*b"US64110L1061")); assert!(validate_isin(*b"US30303M1027")); assert!(validate_isin(*b"CH0031240127")); assert!(validate_isin(*b"CA9861913023")); }
#[test]
fn fail_some_bad_isins() {
assert!(!validate_isin(*b"US5949181040")); assert!(!validate_isin(*b"US38259P5080")); assert!(!validate_isin(*b"US0378331000")); assert!(!validate_isin(*b"BMG491BT1080")); assert!(!validate_isin(*b"IE00B4BNMY30")); assert!(!validate_isin(*b"US0231351060")); assert!(!validate_isin(*b"US64110L1060")); assert!(!validate_isin(*b"US30303M1020")); assert!(!validate_isin(*b"CH0031240120")); assert!(!validate_isin(*b"CA9861913020"));
assert!(!validate_isin(*b"SU5941981045")); assert!(!validate_isin(*b"US3825P95089")); assert!(!validate_isin(*b"US0378313005")); assert!(!validate_isin(*b"BMG491BT0188")); assert!(!validate_isin(*b"IE00B4BNM3Y4")); assert!(!validate_isin(*b"US2031351067")); assert!(!validate_isin(*b"US61410L1061")); assert!(!validate_isin(*b"US30033M1027")); assert!(!validate_isin(*b"CH0032140127")); assert!(!validate_isin(*b"CA9861193023")); }
#[test]
fn readme() {
let mut s = "11111111".to_string();
assert!(!valid(&s));
s.push(checksum(s.as_bytes()) as char);
assert_eq!(s, "111111118");
assert!(valid(&s));
}
}