tangled
alpha
login
or
join now
jonas.tngl.sh
/
advent-of-code
0
fork
atom
Advent of Code solutions in Rust
0
fork
atom
overview
issues
pulls
pipelines
feat: day 16 of AoC 2020
jonas.tngl.sh
3 months ago
aac5fd04
e4c98cd4
1/1
rust.yml
success
4m 20s
+230
-2
2 changed files
expand all
collapse all
unified
split
aoc_2020
src
day16.rs
main.rs
+228
aoc_2020/src/day16.rs
···
1
1
+
use std::{collections::HashSet, ops::RangeInclusive};
2
2
+
3
3
+
use anyhow::{Context, anyhow, bail};
4
4
+
use aoc_companion::prelude::*;
5
5
+
use itertools::Itertools as _;
6
6
+
7
7
+
pub(crate) struct Door<'input> {
8
8
+
rules: Vec<Rule<'input>>,
9
9
+
my_ticket: Vec<u64>,
10
10
+
nearby_tickets: Vec<Vec<u64>>,
11
11
+
}
12
12
+
13
13
+
impl<'input> Solution<'input> for Door<'input> {
14
14
+
fn parse(input: &'input str) -> Result<Self> {
15
15
+
let [rules, my_ticket, nearby_tickets] =
16
16
+
aoc_utils::array::from_iter_exact(input.split("\n\n"))
17
17
+
.map_err(|v| anyhow!("expected three sections, got {} sections", v.len()))?;
18
18
+
let rules = rules.lines().map(parse_rule).try_collect()?;
19
19
+
let my_ticket = my_ticket
20
20
+
.strip_prefix("your ticket:\n")
21
21
+
.ok_or_else(|| anyhow!("missing introducer \"your ticket:\""))?
22
22
+
.split(',')
23
23
+
.map(str::parse)
24
24
+
.try_collect()
25
25
+
.with_context(|| anyhow!("failed to parse my ticket"))?;
26
26
+
let nearby_tickets = nearby_tickets
27
27
+
.strip_prefix("nearby tickets:\n")
28
28
+
.ok_or_else(|| anyhow!("missing introducer \"nearby tickets:\""))?
29
29
+
.lines()
30
30
+
.map(|ticket| {
31
31
+
ticket
32
32
+
.split(',')
33
33
+
.map(str::parse)
34
34
+
.try_collect()
35
35
+
.with_context(|| anyhow!("failed to parse nearby ticket {ticket:?}"))
36
36
+
})
37
37
+
.try_collect()?;
38
38
+
Ok(Self {
39
39
+
rules,
40
40
+
my_ticket,
41
41
+
nearby_tickets,
42
42
+
})
43
43
+
}
44
44
+
45
45
+
fn part1(&self) -> u64 {
46
46
+
self.nearby_tickets
47
47
+
.iter()
48
48
+
.flatten()
49
49
+
.filter(|&&x| !is_valid_for_any_field(x, &self.rules))
50
50
+
.sum()
51
51
+
}
52
52
+
53
53
+
fn part2(&self) -> Result<u64> {
54
54
+
let mapping = find_field_mapping(self.valid_tickets(), &self.rules)?;
55
55
+
Ok(self
56
56
+
.rules
57
57
+
.iter()
58
58
+
.enumerate()
59
59
+
.filter(|(_, rule)| rule.field_name.starts_with("departure"))
60
60
+
.map(|(i, _)| self.my_ticket[mapping[i]])
61
61
+
.product())
62
62
+
}
63
63
+
}
64
64
+
65
65
+
impl Door<'_> {
66
66
+
fn valid_tickets(&self) -> impl Iterator<Item = &[u64]> {
67
67
+
self.nearby_tickets
68
68
+
.iter()
69
69
+
.filter(|ticket| {
70
70
+
ticket
71
71
+
.iter()
72
72
+
.all(|&x| is_valid_for_any_field(x, &self.rules))
73
73
+
})
74
74
+
.map(Vec::as_slice)
75
75
+
}
76
76
+
}
77
77
+
78
78
+
#[derive(Clone, Debug, PartialEq, Eq)]
79
79
+
struct Rule<'input> {
80
80
+
field_name: &'input str,
81
81
+
domain: [RangeInclusive<u64>; 2],
82
82
+
}
83
83
+
84
84
+
fn parse_rule<'input>(s: &'input str) -> Result<Rule<'input>> {
85
85
+
let Some((field_name, ranges)) = s.split_once(':') else {
86
86
+
bail!("missing colon in rule {s:?}");
87
87
+
};
88
88
+
Ok(Rule {
89
89
+
field_name,
90
90
+
domain: aoc_utils::array::try_from_iter_exact(ranges.trim_start().split("or").map(|r| {
91
91
+
r.trim()
92
92
+
.split_once('-')
93
93
+
.ok_or_else(|| anyhow!("missing dash in range {r:?}"))
94
94
+
.and_then(|(from, to)| Ok(from.parse()?..=to.parse()?))
95
95
+
.with_context(|| anyhow!("failed to parse range {r:?}"))
96
96
+
}))?
97
97
+
.map_err(|v| anyhow!("expected exactly two ranges, got {}", v.len()))?,
98
98
+
})
99
99
+
}
100
100
+
101
101
+
fn is_valid_for_any_field(x: u64, rules: &[Rule]) -> bool {
102
102
+
rules
103
103
+
.iter()
104
104
+
.flat_map(|rule| rule.domain.iter())
105
105
+
.any(|r| r.contains(&x))
106
106
+
}
107
107
+
108
108
+
fn find_field_mapping<'a>(
109
109
+
tickets: impl IntoIterator<Item = &'a [u64]>,
110
110
+
rules: &[Rule],
111
111
+
) -> Result<Vec<usize>> {
112
112
+
let mut possible_indices = vec![HashSet::<_>::from_iter(0..rules.len()); rules.len()];
113
113
+
114
114
+
for ticket in tickets {
115
115
+
for (i, field) in ticket.iter().enumerate() {
116
116
+
for (rule_indices, rule) in possible_indices.iter_mut().zip(rules) {
117
117
+
if !rule.domain.iter().any(|r| r.contains(field)) {
118
118
+
rule_indices.remove(&i);
119
119
+
}
120
120
+
}
121
121
+
}
122
122
+
}
123
123
+
124
124
+
let mut res = vec![None; rules.len()];
125
125
+
while let Some((i, s)) = possible_indices
126
126
+
.iter()
127
127
+
.enumerate()
128
128
+
.find(|(_, s)| s.len() == 1)
129
129
+
{
130
130
+
let j = *s.iter().next().unwrap();
131
131
+
res[i] = Some(j);
132
132
+
for p in &mut possible_indices {
133
133
+
p.remove(&j);
134
134
+
}
135
135
+
}
136
136
+
137
137
+
res.into_iter()
138
138
+
.map(|o| o.ok_or_else(|| anyhow!("field mapping ambiguous")))
139
139
+
.try_collect()
140
140
+
}
141
141
+
142
142
+
#[cfg(test)]
143
143
+
mod tests {
144
144
+
use super::*;
145
145
+
146
146
+
const EXAMPLE_INPUT: &str = "\
147
147
+
class: 1-3 or 5-7
148
148
+
row: 6-11 or 33-44
149
149
+
seat: 13-40 or 45-50
150
150
+
151
151
+
your ticket:
152
152
+
7,1,14
153
153
+
154
154
+
nearby tickets:
155
155
+
7,3,47
156
156
+
40,4,50
157
157
+
55,2,20
158
158
+
38,6,12";
159
159
+
160
160
+
const EXAMPLE_RULES: &[Rule] = &[
161
161
+
Rule {
162
162
+
field_name: "class",
163
163
+
domain: [1..=3, 5..=7],
164
164
+
},
165
165
+
Rule {
166
166
+
field_name: "row",
167
167
+
domain: [6..=11, 33..=44],
168
168
+
},
169
169
+
Rule {
170
170
+
field_name: "seat",
171
171
+
domain: [13..=40, 45..=50],
172
172
+
},
173
173
+
];
174
174
+
175
175
+
const EXAMPLE_MY_TICKET: [u64; 3] = [7, 1, 14];
176
176
+
177
177
+
const EXAMPLE_NEARBY_TICKETS: &[[u64; 3]] =
178
178
+
&[[7, 3, 47], [40, 4, 50], [55, 2, 20], [38, 6, 12]];
179
179
+
180
180
+
const SECOND_EXAMPLE_INPUT: &str = "\
181
181
+
class: 0-1 or 4-19
182
182
+
row: 0-5 or 8-19
183
183
+
seat: 0-13 or 16-19
184
184
+
185
185
+
your ticket:
186
186
+
11,12,13
187
187
+
188
188
+
nearby tickets:
189
189
+
3,9,18
190
190
+
15,1,5
191
191
+
5,14,9";
192
192
+
193
193
+
#[test]
194
194
+
fn parse_example_input() {
195
195
+
let Door {
196
196
+
rules,
197
197
+
my_ticket,
198
198
+
nearby_tickets,
199
199
+
} = Door::parse(EXAMPLE_INPUT).unwrap();
200
200
+
itertools::assert_equal(&rules, EXAMPLE_RULES);
201
201
+
assert_eq!(my_ticket, EXAMPLE_MY_TICKET);
202
202
+
itertools::assert_equal(&nearby_tickets, EXAMPLE_NEARBY_TICKETS);
203
203
+
}
204
204
+
205
205
+
#[test]
206
206
+
fn find_invalid_values_in_example() {
207
207
+
itertools::assert_equal(
208
208
+
EXAMPLE_NEARBY_TICKETS
209
209
+
.iter()
210
210
+
.flatten()
211
211
+
.filter(|&&x| !is_valid_for_any_field(x, EXAMPLE_RULES)),
212
212
+
&[4, 55, 12],
213
213
+
);
214
214
+
}
215
215
+
216
216
+
#[test]
217
217
+
fn find_field_mapping_in_second_example() {
218
218
+
let Door {
219
219
+
rules,
220
220
+
my_ticket: _,
221
221
+
nearby_tickets,
222
222
+
} = Door::parse(SECOND_EXAMPLE_INPUT).unwrap();
223
223
+
assert_eq!(
224
224
+
find_field_mapping(nearby_tickets.iter().map(Vec::as_slice), &rules).unwrap(),
225
225
+
[1, 0, 2]
226
226
+
);
227
227
+
}
228
228
+
}
+2
-2
aoc_2020/src/main.rs
···
15
15
mod day13;
16
16
mod day14;
17
17
mod day15;
18
18
-
// mod day16;
18
18
+
mod day16;
19
19
// mod day17;
20
20
// mod day18;
21
21
// mod day19;
···
46
46
door!(2020-12-13 ~> day13),
47
47
door!(2020-12-14 ~> day14),
48
48
door!(2020-12-15 ~> day15),
49
49
-
// door!(2020-12-16 ~> day16),
49
49
+
door!(2020-12-16 ~> day16),
50
50
// door!(2020-12-17 ~> day17),
51
51
// door!(2020-12-18 ~> day18),
52
52
// door!(2020-12-19 ~> day19),