1use utils::prelude::*;
2
3#[derive(Clone, Debug)]
8pub struct Day16 {
9 part1: u64,
10 part2: u64,
11}
12
13const MAX_FIELDS: usize = 20;
14const MAX_VALUE: u16 = 999;
15
16impl Day16 {
17 pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
18 let number = parser::number_range(0..=MAX_VALUE);
19 let range = number
20 .then(number.with_prefix(b'-'))
21 .map_res(|(start, end)| {
22 (start <= end)
23 .then_some((start, end))
24 .ok_or("range end cannot be less than start")
25 });
26 let name = parser::take_while1(u8::is_ascii_lowercase)
27 .repeat_fold(b' ', 1, (), |(), _| ())
28 .with_consumed()
29 .map(|(_, name)| name)
30 .with_suffix(": ");
31 let rule = name
32 .then(range.repeat_n::<2, _>(" or "))
33 .map_res(|(name, ranges)| {
34 (ranges[0].1 < ranges[1].0)
35 .then_some((name, ranges))
36 .ok_or("ranges must be sorted and disjoint")
37 });
38 let ticket = number
39 .repeat_arrayvec::<MAX_FIELDS, _>(b',', 1)
40 .with_consumed();
41
42 let (rules, your_ticket, nearby_tickets) = rule
43 .repeat_arrayvec::<MAX_FIELDS, _>(parser::eol(), 1)
44 .with_eol()
45 .with_eol()
46 .then(
47 ticket
48 .with_prefix("your ticket:".with_eol())
49 .with_eol()
50 .with_eol(),
51 )
52 .then(
53 ticket
54 .repeat(parser::eol(), 1)
55 .with_prefix("nearby tickets:".with_eol()),
56 )
57 .parse_complete(input)?;
58
59 for (index, &(name, _)) in rules.iter().enumerate() {
60 if rules[..index].iter().any(|(other, _)| *other == name) {
61 return Err(InputError::new(input, name, "duplicate field name"));
62 }
63 }
64 for (ticket, text) in nearby_tickets.iter().chain([&your_ticket]) {
65 if ticket.len() != rules.len() {
66 return Err(InputError::new(
67 input,
68 *text,
69 "ticket field count does not match rule count",
70 ));
71 }
72 }
73
74 let all_fields = (1u32 << rules.len()) - 1;
75 let departure_fields = rules
76 .iter()
77 .enumerate()
78 .filter(|(_, (name, _))| name.starts_with(b"departure"))
79 .fold(0, |mask, (field, _)| mask | 1 << field);
80
81 let mut valid_fields = [0u32; MAX_VALUE as usize + 2];
85 for (field, (_, ranges)) in rules.iter().enumerate() {
86 for &(start, end) in ranges {
87 valid_fields[usize::from(start)] ^= 1 << field;
88 valid_fields[usize::from(end) + 1] ^= 1 << field;
89 }
90 }
91 let mut fields = 0;
92 for change in &mut valid_fields {
93 fields ^= *change;
94 *change = fields;
95 }
96
97 let (your_ticket, your_text) = your_ticket;
98 if your_ticket
99 .iter()
100 .any(|&value| valid_fields[usize::from(value)] == 0)
101 {
102 return Err(InputError::new(
103 input,
104 your_text,
105 "your ticket contains an invalid value",
106 ));
107 }
108
109 let mut part1 = 0;
110 let mut candidates = [all_fields; MAX_FIELDS];
111 let mut column_fields = [0; MAX_FIELDS];
112 for (ticket, _) in nearby_tickets {
113 let mut valid_ticket = true;
114 for (column, &value) in ticket.iter().enumerate() {
115 column_fields[column] = valid_fields[usize::from(value)];
116 if column_fields[column] == 0 {
117 part1 += u64::from(value);
118 valid_ticket = false;
119 }
120 }
121
122 if valid_ticket {
123 for column in 0..rules.len() {
124 candidates[column] &= column_fields[column];
125 }
126 }
127 }
128
129 let mut part2 = 1u64;
130 for _ in 0..rules.len() {
131 let Some(column) = candidates[..rules.len()]
132 .iter()
133 .position(|fields| fields.count_ones() == 1)
134 else {
135 return Err(InputError::new(
136 input,
137 0,
138 "expected unique field assignment",
139 ));
140 };
141
142 let field = candidates[column];
143 candidates[..rules.len()]
144 .iter_mut()
145 .for_each(|fields| *fields &= !field);
146
147 if field & departure_fields != 0 {
148 part2 *= u64::from(your_ticket[column]);
149 }
150 }
151
152 Ok(Self { part1, part2 })
153 }
154
155 #[must_use]
156 pub fn part1(&self) -> u64 {
157 self.part1
158 }
159
160 #[must_use]
161 pub fn part2(&self) -> u64 {
162 self.part2
163 }
164}
165
166examples!(Day16 -> (u64, u64) [
167 {file: "day16_example0.txt", part1: 71},
168]);