advent_of_code/year2020/
day16.rs1use crate::input::Input;
2
3pub fn solve(input: &Input) -> Result<u64, String> {
4 const MAX_FIELD_VALUE: u32 = 1024;
5
6 let mut parts = input.text.splitn(3, "\n\n");
7 let on_error = || "Invalid input".to_string();
8 let map_error = |_| on_error();
9
10 let ticket_fields_str = parts.next().ok_or_else(on_error)?;
11 let your_ticket_str = parts.next().ok_or_else(on_error)?;
12 let nearby_tickets_str = parts.next().ok_or_else(on_error)?;
13
14 let mut your_ticket_values = Vec::new();
15 for part in your_ticket_str
16 .lines()
17 .nth(1)
18 .ok_or_else(on_error)?
19 .split(',')
20 {
21 your_ticket_values.push(part.parse::<u32>().map_err(map_error)?);
22 }
23
24 let mut departure_fields = Vec::with_capacity(6);
25 let mut field_ranges = Vec::new();
26 for line in ticket_fields_str.lines() {
27 let mut field_range = vec![false; (MAX_FIELD_VALUE + 1) as usize];
28
29 let mut line_parts = line.splitn(2, ": ");
30
31 let field_name = line_parts.next().ok_or_else(on_error)?;
32 if field_name.starts_with("departure") {
33 departure_fields.push(field_ranges.len() as u32);
34 }
35
36 let or_str = line_parts.next().ok_or_else(on_error)?;
37 let mut line_parts = or_str.splitn(2, " or ");
38 for _ in 0..2 {
39 let range_str = line_parts.next().ok_or_else(on_error)?;
40 let mut range_parts = range_str.splitn(2, '-');
41
42 let range_start = range_parts
43 .next()
44 .ok_or_else(on_error)?
45 .parse::<u32>()
46 .map_err(map_error)?;
47 let range_end = range_parts
48 .next()
49 .ok_or_else(on_error)?
50 .parse::<u32>()
51 .map_err(map_error)?;
52
53 if range_start >= range_end {
54 return Err(format!("Invalid range: {range_start}-{range_end}"));
55 } else if range_end > MAX_FIELD_VALUE {
56 return Err(format!(
57 "Too high field range (max: {MAX_FIELD_VALUE}): {range_end}",
58 ));
59 }
60
61 for value in range_start..=range_end {
62 field_range[value as usize] = true;
63 }
64 }
65 field_ranges.push(field_range);
66 }
67
68 if your_ticket_values.len() != field_ranges.len() {
69 return Err(format!(
70 "Your ticket contains {} fields, but {} fields are specified",
71 your_ticket_values.len(),
72 field_ranges.len()
73 ));
74 } else if field_ranges.len() > 32 {
75 return Err(format!(
76 "Max 32 fields supported (input had {})",
77 field_ranges.len()
78 ));
79 }
80
81 let possibilities_bitmask: u32 = (1 << field_ranges.len()) as u32 - 1;
83 let mut possible_fields_for_position = vec![possibilities_bitmask; your_ticket_values.len()];
84
85 let mut error_rate = 0;
86 for line in nearby_tickets_str.lines().skip(1) {
87 'outer: for (field_position, value_str) in line.split(',').enumerate() {
88 let value = value_str.parse::<u32>().map_err(map_error)?;
89 if value > MAX_FIELD_VALUE {
90 return Err(format!("Invalid field value: {value}"));
91 }
92 let valid_ticket = field_ranges.iter().any(|range| range[value as usize]);
93 if valid_ticket {
94 if input.is_part_one() {
95 continue 'outer;
96 } else {
97 for (field_idx, range) in field_ranges.iter().enumerate() {
98 if !range[value as usize] {
99 possible_fields_for_position[field_position] &= !(1 << field_idx);
100 }
101 }
102 }
103 }
104 error_rate += value;
105 }
106 }
107
108 if input.is_part_one() {
109 Ok(u64::from(error_rate))
110 } else {
111 let mut departure_values_multiplied = 1_u64;
112 let mut identified_positions = vec![false; field_ranges.len()];
113
114 loop {
115 let mut any_change = false;
116 for position in 0..your_ticket_values.len() {
117 let possible_fields = possible_fields_for_position[position];
118 if possible_fields.count_ones() == 1 && !identified_positions[position] {
119 identified_positions[position] = true;
120 any_change = true;
121
122 let field_idx = possible_fields.trailing_zeros();
123 if departure_fields.contains(&field_idx) {
124 departure_values_multiplied *= u64::from(your_ticket_values[position]);
125 }
126
127 let clear_possibility_bitmask = !(1 << field_idx);
128 for (idx, possible_fields) in
129 possible_fields_for_position.iter_mut().enumerate()
130 {
131 if idx != position {
132 *possible_fields &= clear_possibility_bitmask;
133 }
134 }
135 }
136 }
137 if !any_change {
138 break;
139 }
140 }
141
142 Ok(departure_values_multiplied)
143 }
144}
145
146#[test]
147pub fn tests() {
148 let example_part_one = "class: 1-3 or 5-7
149row: 6-11 or 33-44
150seat: 13-40 or 45-50
151
152your ticket:
1537,1,14
154
155nearby tickets:
1567,3,47
15740,4,50
15855,2,20
15938,6,12";
160 test_part_one!(example_part_one => 71);
161
162 let example_part_two = "class: 0-1 or 4-19
163row: 0-5 or 8-19
164seat: 0-13 or 16-19
165
166your ticket:
16711,12,13
168
169nearby tickets:
1703,9,18
17115,1,5
1725,14,9";
173 test_part_two!(example_part_two => 1);
174
175 let real_input = include_str!("day16_input.txt");
176 test_part_one!(real_input => 29019);
177 test_part_two!(real_input => 517_827_547_723);
178}