1use crate::util::iter::*;
8use crate::util::parse::*;
9use std::ops::RangeInclusive;
10
11type Input = (u32, u32);
12
13pub fn parse(input: &str) -> Input {
14 let mut passport = Vec::new();
15
16 input.split("\n\n").fold((0, 0), |(part_one, part_two), block| {
17 passport.clear();
18 passport
19 .extend(block.split([':', ' ', '\n']).chunk::<2>().filter(|&[key, _]| key != "cid"));
20
21 if passport.len() == 7 {
22 (part_one + 1, part_two + passport.iter().all(validate_field) as u32)
23 } else {
24 (part_one, part_two)
25 }
26 })
27}
28
29pub fn part1(input: &Input) -> u32 {
30 input.0
31}
32
33pub fn part2(input: &Input) -> u32 {
34 input.1
35}
36
37fn validate_field(&[key, value]: &[&str; 2]) -> bool {
38 match key {
39 "byr" => validate_range(value, 1920..=2002),
40 "iyr" => validate_range(value, 2010..=2020),
41 "eyr" => validate_range(value, 2020..=2030),
42 "hgt" => validate_height(value),
43 "hcl" => validate_hair_color(value),
44 "ecl" => validate_eye_color(value),
45 "pid" => validate_passport_id(value),
46 _ => unreachable!(),
47 }
48}
49
50fn validate_range(s: &str, range: RangeInclusive<u32>) -> bool {
51 range.contains(&s.unsigned())
52}
53
54fn validate_height(hgt: &str) -> bool {
55 if let Some(n) = hgt.strip_suffix("in") {
56 validate_range(n, 59..=76)
57 } else if let Some(n) = hgt.strip_suffix("cm") {
58 validate_range(n, 150..=193)
59 } else {
60 false
61 }
62}
63
64fn validate_hair_color(hcl: &str) -> bool {
65 let hcl = hcl.as_bytes();
66 hcl.len() == 7 && hcl[0] == b'#' && hcl[1..].iter().all(u8::is_ascii_hexdigit)
67}
68
69fn validate_eye_color(ecl: &str) -> bool {
70 matches!(ecl, "amb" | "blu" | "brn" | "gry" | "grn" | "hzl" | "oth")
71}
72
73fn validate_passport_id(pid: &str) -> bool {
74 pid.len() == 9 && pid.bytes().all(|b| b.is_ascii_digit())
75}