Skip to main content

advent_of_code/year2020/
day16.rs

1use 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    // Set the lowest field_ranges.len() bits:
82    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}