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 11 of AoC 2025
jonas.tngl.sh
3 months ago
c5127164
1fb71d3e
1/1
rust.yml
success
3m 28s
+222
-2
2 changed files
expand all
collapse all
unified
split
aoc_2025
src
day11.rs
main.rs
+220
aoc_2025/src/day11.rs
···
1
1
+
use std::collections::{HashMap, HashSet};
2
2
+
3
3
+
use anyhow::bail;
4
4
+
use aoc_companion::prelude::*;
5
5
+
use itertools::Itertools as _;
6
6
+
7
7
+
pub(crate) struct Door {
8
8
+
connections: HashMap<Device, HashSet<Device>>,
9
9
+
}
10
10
+
11
11
+
impl<'input> Solution<'input> for Door {
12
12
+
fn parse(input: &'input str) -> Result<Self> {
13
13
+
input
14
14
+
.lines()
15
15
+
.map(|line| {
16
16
+
let Some((this_name, connected_names)) = line.split_once(": ") else {
17
17
+
bail!("expected \": \" delimiter, found {line:?}");
18
18
+
};
19
19
+
let connected_devices = connected_names
20
20
+
.split_ascii_whitespace()
21
21
+
.map(Device::new)
22
22
+
.try_collect()?;
23
23
+
Ok((Device::new(this_name)?, connected_devices))
24
24
+
})
25
25
+
.try_collect()
26
26
+
.map(|connections| Door { connections })
27
27
+
}
28
28
+
29
29
+
fn part1(&self) -> usize {
30
30
+
number_of_paths(YOU, &self.connections)
31
31
+
}
32
32
+
33
33
+
fn part2(&self) -> usize {
34
34
+
number_of_problematic_paths(SVR, &self.connections).num_both
35
35
+
}
36
36
+
}
37
37
+
38
38
+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
39
39
+
struct Device([u8; 3]);
40
40
+
41
41
+
impl std::fmt::Debug for Device {
42
42
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43
43
+
let s = str::from_utf8(&self.0).expect("device names should be valid UTF-8");
44
44
+
f.debug_tuple("Device").field(&s).finish()
45
45
+
}
46
46
+
}
47
47
+
48
48
+
impl Device {
49
49
+
fn new(name: &str) -> Result<Self> {
50
50
+
if name.len() != 3 {
51
51
+
bail!(
52
52
+
"device name {name:?} needs to be exactly three bytes, but is {} bytes: {:x?}",
53
53
+
name.len(),
54
54
+
name.as_bytes()
55
55
+
);
56
56
+
}
57
57
+
let mut bytes = [0; 3];
58
58
+
bytes.copy_from_slice(name.as_bytes());
59
59
+
Ok(Self(bytes))
60
60
+
}
61
61
+
}
62
62
+
63
63
+
const OUT: Device = Device(*b"out");
64
64
+
const YOU: Device = Device(*b"you");
65
65
+
const SVR: Device = Device(*b"svr");
66
66
+
const DAC: Device = Device(*b"dac");
67
67
+
const FFT: Device = Device(*b"fft");
68
68
+
69
69
+
fn number_of_paths(from: Device, connections: &HashMap<Device, HashSet<Device>>) -> usize {
70
70
+
if from == OUT {
71
71
+
return 1;
72
72
+
}
73
73
+
74
74
+
connections[&from]
75
75
+
.iter()
76
76
+
.map(|&to| number_of_paths(to, connections))
77
77
+
.sum()
78
78
+
}
79
79
+
80
80
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
81
81
+
struct PathStats {
82
82
+
num_neither: usize,
83
83
+
num_only_dac: usize,
84
84
+
num_only_fft: usize,
85
85
+
num_both: usize,
86
86
+
}
87
87
+
88
88
+
impl std::iter::Sum for PathStats {
89
89
+
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
90
90
+
iter.fold(Self::default(), |acc, res| Self {
91
91
+
num_neither: acc.num_neither + res.num_neither,
92
92
+
num_only_dac: acc.num_only_dac + res.num_only_dac,
93
93
+
num_only_fft: acc.num_only_fft + res.num_only_fft,
94
94
+
num_both: acc.num_both + res.num_both,
95
95
+
})
96
96
+
}
97
97
+
}
98
98
+
99
99
+
fn number_of_problematic_paths(
100
100
+
start: Device,
101
101
+
connections: &HashMap<Device, HashSet<Device>>,
102
102
+
) -> PathStats {
103
103
+
aoc_utils::cache::cached(|from, recurse| {
104
104
+
if from == OUT {
105
105
+
return PathStats {
106
106
+
num_neither: 1,
107
107
+
..Default::default()
108
108
+
};
109
109
+
}
110
110
+
111
111
+
connections[&from]
112
112
+
.iter()
113
113
+
.map(|&to| recurse(to))
114
114
+
.map(|stats| match from {
115
115
+
DAC => PathStats {
116
116
+
num_neither: 0,
117
117
+
num_only_dac: stats.num_neither,
118
118
+
num_only_fft: 0,
119
119
+
num_both: stats.num_only_fft,
120
120
+
},
121
121
+
FFT => PathStats {
122
122
+
num_neither: 0,
123
123
+
num_only_dac: 0,
124
124
+
num_only_fft: stats.num_neither,
125
125
+
num_both: stats.num_only_dac,
126
126
+
},
127
127
+
_ => stats,
128
128
+
})
129
129
+
.sum()
130
130
+
})(start)
131
131
+
}
132
132
+
133
133
+
#[cfg(test)]
134
134
+
mod tests {
135
135
+
use super::*;
136
136
+
137
137
+
const FIRST_EXAMPLE_INPUT: &str = "\
138
138
+
aaa: you hhh
139
139
+
you: bbb ccc
140
140
+
bbb: ddd eee
141
141
+
ccc: ddd eee fff
142
142
+
ddd: ggg
143
143
+
eee: out
144
144
+
fff: out
145
145
+
ggg: out
146
146
+
hhh: ccc fff iii
147
147
+
iii: out";
148
148
+
149
149
+
fn first_example_connections() -> HashMap<Device, HashSet<Device>> {
150
150
+
HashMap::from([
151
151
+
(
152
152
+
Device(*b"aaa"),
153
153
+
HashSet::from([Device(*b"you"), Device(*b"hhh")]),
154
154
+
),
155
155
+
(
156
156
+
Device(*b"you"),
157
157
+
HashSet::from([Device(*b"bbb"), Device(*b"ccc")]),
158
158
+
),
159
159
+
(
160
160
+
Device(*b"bbb"),
161
161
+
HashSet::from([Device(*b"ddd"), Device(*b"eee")]),
162
162
+
),
163
163
+
(
164
164
+
Device(*b"ccc"),
165
165
+
HashSet::from([Device(*b"ddd"), Device(*b"eee"), Device(*b"fff")]),
166
166
+
),
167
167
+
(Device(*b"ddd"), HashSet::from([Device(*b"ggg")])),
168
168
+
(Device(*b"eee"), HashSet::from([Device(*b"out")])),
169
169
+
(Device(*b"fff"), HashSet::from([Device(*b"out")])),
170
170
+
(Device(*b"ggg"), HashSet::from([Device(*b"out")])),
171
171
+
(
172
172
+
Device(*b"hhh"),
173
173
+
HashSet::from([Device(*b"ccc"), Device(*b"fff"), Device(*b"iii")]),
174
174
+
),
175
175
+
(Device(*b"iii"), HashSet::from([Device(*b"out")])),
176
176
+
])
177
177
+
}
178
178
+
179
179
+
const SECOND_EXAMPLE_INPUT: &str = "\
180
180
+
svr: aaa bbb
181
181
+
aaa: fft
182
182
+
fft: ccc
183
183
+
bbb: tty
184
184
+
tty: ccc
185
185
+
ccc: ddd eee
186
186
+
ddd: hub
187
187
+
hub: fff
188
188
+
eee: dac
189
189
+
dac: fff
190
190
+
fff: ggg hhh
191
191
+
ggg: out
192
192
+
hhh: out";
193
193
+
194
194
+
#[test]
195
195
+
fn parse_example_input() {
196
196
+
assert_eq!(
197
197
+
Door::parse(FIRST_EXAMPLE_INPUT).unwrap().connections,
198
198
+
first_example_connections(),
199
199
+
);
200
200
+
}
201
201
+
202
202
+
#[test]
203
203
+
fn find_number_of_paths_to_out() {
204
204
+
assert_eq!(number_of_paths(YOU, &first_example_connections()), 5);
205
205
+
}
206
206
+
207
207
+
#[test]
208
208
+
fn find_number_of_problematic_paths_to_out() {
209
209
+
let Door { connections } = Door::parse(SECOND_EXAMPLE_INPUT).unwrap();
210
210
+
assert_eq!(
211
211
+
number_of_problematic_paths(SVR, &connections),
212
212
+
PathStats {
213
213
+
num_neither: 2,
214
214
+
num_only_dac: 2,
215
215
+
num_only_fft: 2,
216
216
+
num_both: 2
217
217
+
}
218
218
+
)
219
219
+
}
220
220
+
}
+2
-2
aoc_2025/src/main.rs
···
10
10
mod day08;
11
11
mod day09;
12
12
mod day10;
13
13
-
// mod day11;
13
13
+
mod day11;
14
14
// mod day12;
15
15
16
16
use aoc_companion::prelude::*;
···
28
28
door!(2025-12-08 ~> day08),
29
29
door!(2025-12-09 ~> day09),
30
30
door!(2025-12-10 ~> day10),
31
31
-
// door!(2025-12-11 ~> day11),
31
31
+
door!(2025-12-11 ~> day11),
32
32
// door!(2025-12-12 ~> day12),
33
33
])
34
34
.await