1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
5pub struct Day04 {
6 part1: usize,
7 part2: usize,
8}
9
10const BIRTH_YEAR: u8 = 0;
11const ISSUE_YEAR: u8 = 1;
12const EXPIRATION_YEAR: u8 = 2;
13const HEIGHT: u8 = 3;
14const HAIR_COLOR: u8 = 4;
15const EYE_COLOR: u8 = 5;
16const PASSPORT_ID: u8 = 6;
17const COUNTRY_ID: u8 = 7;
18const REQUIRED_FIELDS: u8 = 0b0111_1111;
19
20impl Day04 {
21 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
22 let key = parser::literal_map!(
23 "byr:" => BIRTH_YEAR,
24 "iyr:" => ISSUE_YEAR,
25 "eyr:" => EXPIRATION_YEAR,
26 "hgt:" => HEIGHT,
27 "hcl:" => HAIR_COLOR,
28 "ecl:" => EYE_COLOR,
29 "pid:" => PASSPORT_ID,
30 "cid:" => COUNTRY_ID,
31 );
32 let value = parser::take_while1(u8::is_ascii_graphic);
33 let kv = parser::parse_tree!(
34 (k @ key, v @ value) =>> {
35 (b' ') => (k, v, false),
36 (parser::eol()) =>> {
37 (parser::eol()) => (k, v, true),
38 (parser::noop()) => (k, v, false),
39 },
40 },
41 );
42
43 let (mut part1, mut part2) = (0, 0);
44 let (mut fields, mut valid) = (0, true);
45 for entry in kv.parse_iterator(input) {
46 let (field, value, end) = entry?;
47
48 let bit = 1 << field;
49 if fields & bit != 0 {
50 return Err(InputError::new(input, value, "duplicate passport field"));
51 }
52 fields |= bit;
53 valid &= Self::valid_value(field, value);
54
55 if end {
56 let complete = fields & REQUIRED_FIELDS == REQUIRED_FIELDS;
57 part1 += usize::from(complete);
58 part2 += usize::from(complete & valid);
59 (fields, valid) = (0, true);
60 }
61 }
62
63 if fields != 0 {
64 return Err(InputError::new(
65 input,
66 input.len(),
67 "expected passport field after space",
68 ));
69 }
70
71 Ok(Self { part1, part2 })
72 }
73
74 #[inline]
75 fn valid_value(index: u8, value: &[u8]) -> bool {
76 match index {
77 BIRTH_YEAR => Self::num_range(value, b"1920", b"2002"),
78 ISSUE_YEAR => Self::num_range(value, b"2010", b"2020"),
79 EXPIRATION_YEAR => Self::num_range(value, b"2020", b"2030"),
80 HEIGHT => match value {
81 [value @ .., b'c', b'm'] => Self::num_range(value, b"150", b"193"),
82 [value @ .., b'i', b'n'] => Self::num_range(value, b"59", b"76"),
83 _ => false,
84 },
85 HAIR_COLOR => {
86 value.len() == 7
87 && value[0] == b'#'
88 && value[1..]
89 .iter()
90 .all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f'))
91 }
92 EYE_COLOR => {
93 matches!(
94 value,
95 b"amb" | b"blu" | b"brn" | b"gry" | b"grn" | b"hzl" | b"oth"
96 )
97 }
98 PASSPORT_ID => value.len() == 9 && value.iter().all(u8::is_ascii_digit),
99 COUNTRY_ID => true,
100 _ => unreachable!(),
101 }
102 }
103
104 #[inline]
105 fn num_range<const N: usize>(value: &[u8], min: &[u8; N], max: &[u8; N]) -> bool {
106 value.len() == N && value.iter().all(u8::is_ascii_digit) && value >= min && value <= max
107 }
108
109 #[must_use]
110 pub fn part1(&self) -> usize {
111 self.part1
112 }
113
114 #[must_use]
115 pub fn part2(&self) -> usize {
116 self.part2
117 }
118}
119
120examples!(Day04 -> (usize, usize) [
121 {file: "day04_example0.txt", part1: 2},
122 {file: "day04_example1.txt", part2: 0},
123 {file: "day04_example2.txt", part2: 4},
124]);