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