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 4 of AoC 2025
jonas.tngl.sh
3 months ago
02cec4e5
c87b8ab4
1/1
rust.yml
success
8m 52s
+220
-2
4 changed files
expand all
collapse all
unified
split
Cargo.lock
aoc_2025
Cargo.toml
src
day04.rs
main.rs
+1
Cargo.lock
···
150
150
"aoc_utils",
151
151
"itertools",
152
152
"num-traits",
153
153
+
"thiserror",
153
154
"tokio",
154
155
]
155
156
+1
aoc_2025/Cargo.toml
···
11
11
anyhow = { workspace = true }
12
12
itertools = { workspace = true }
13
13
num-traits = { workspace = true }
14
14
+
thiserror = { workspace = true }
14
15
tokio = { workspace = true }
+216
aoc_2025/src/day04.rs
···
1
1
+
use std::collections::HashSet;
2
2
+
3
3
+
use aoc_companion::prelude::*;
4
4
+
use aoc_utils::geometry::{ParseMapError, Point};
5
5
+
use aoc_utils::linalg::Vector;
6
6
+
use itertools::Itertools;
7
7
+
8
8
+
pub(crate) struct Door {
9
9
+
rolls: HashSet<Vector<usize, 2>>,
10
10
+
}
11
11
+
12
12
+
impl<'input> Solution<'input> for Door {
13
13
+
fn parse(input: &'input str) -> Result<Self, ParseMapError<ParseTileError>> {
14
14
+
Ok(Door {
15
15
+
rolls: parse_roll_locations(input)?,
16
16
+
})
17
17
+
}
18
18
+
19
19
+
fn part1(&self) -> usize {
20
20
+
accessible_roll_locations(&self.rolls).count()
21
21
+
}
22
22
+
23
23
+
fn part2(&self) -> usize {
24
24
+
self.rolls.len() - non_removable_roll_locations(self.rolls.clone()).len()
25
25
+
}
26
26
+
}
27
27
+
28
28
+
#[derive(Debug, thiserror::Error)]
29
29
+
pub(crate) enum ParseTileError {
30
30
+
#[error("invalid map tile: {tile:?}")]
31
31
+
InvalidTile { tile: u8 },
32
32
+
}
33
33
+
34
34
+
fn parse_roll_locations(
35
35
+
input: &str,
36
36
+
) -> Result<HashSet<Vector<usize, 2>>, ParseMapError<ParseTileError>> {
37
37
+
let map = aoc_utils::geometry::try_parse_map(input, |tile| match tile {
38
38
+
b'@' => Ok(true),
39
39
+
b'.' => Ok(false),
40
40
+
_ => Err(ParseTileError::InvalidTile { tile }),
41
41
+
})?;
42
42
+
43
43
+
Ok(map
44
44
+
.indexed_iter()
45
45
+
.filter(|(_, is_roll)| **is_roll)
46
46
+
.map(|((y, x), _)| Vector([x, y]))
47
47
+
.collect())
48
48
+
}
49
49
+
50
50
+
fn accessible_roll_locations(
51
51
+
rolls: &HashSet<Vector<usize, 2>>,
52
52
+
) -> impl Iterator<Item = Vector<usize, 2>> {
53
53
+
rolls
54
54
+
.iter()
55
55
+
.filter(|r| r.neighbors().filter(|n| rolls.contains(n)).count() < 4)
56
56
+
.copied()
57
57
+
}
58
58
+
59
59
+
fn with_accessible_rolls_removed(rolls: &HashSet<Vector<usize, 2>>) -> HashSet<Vector<usize, 2>> {
60
60
+
rolls
61
61
+
.difference(&accessible_roll_locations(rolls).collect())
62
62
+
.copied()
63
63
+
.collect()
64
64
+
}
65
65
+
66
66
+
fn non_removable_roll_locations(rolls: HashSet<Vector<usize, 2>>) -> HashSet<Vector<usize, 2>> {
67
67
+
std::iter::successors(Some(rolls), |prev| {
68
68
+
Some(with_accessible_rolls_removed(prev))
69
69
+
})
70
70
+
.tuple_windows()
71
71
+
.find_map(|(lhs, rhs)| (lhs == rhs).then_some(lhs))
72
72
+
.unwrap()
73
73
+
}
74
74
+
75
75
+
#[cfg(test)]
76
76
+
mod tests {
77
77
+
use itertools::Itertools;
78
78
+
79
79
+
use super::*;
80
80
+
81
81
+
const EXAMPLE_INPUT: &str = "\
82
82
+
..@@.@@@@.
83
83
+
@@@.@.@.@@
84
84
+
@@@@@.@.@@
85
85
+
@.@@@@..@.
86
86
+
@@.@@@@.@@
87
87
+
.@@@@@@@.@
88
88
+
.@.@.@.@@@
89
89
+
@.@@@.@@@@
90
90
+
.@@@@@@@@.
91
91
+
@.@.@@@.@.";
92
92
+
93
93
+
const EXAMPLE_ACCESSIBLE_ROLLS: &str = "\
94
94
+
..@@.@@.@.
95
95
+
@.........
96
96
+
......@...
97
97
+
..........
98
98
+
@........@
99
99
+
..........
100
100
+
..........
101
101
+
@.........
102
102
+
..........
103
103
+
@.@.....@.";
104
104
+
105
105
+
const EXAMPLE_NON_REMOVABLE_ROLLS: &str = "\
106
106
+
..........
107
107
+
..........
108
108
+
..........
109
109
+
....@@....
110
110
+
...@@@@...
111
111
+
...@@@@@..
112
112
+
...@.@.@@.
113
113
+
...@@.@@@.
114
114
+
...@@@@@..
115
115
+
....@@@...";
116
116
+
117
117
+
const EXAMPLE_ROLL_LOCATIONS: &[Vector<usize, 2>] = &[
118
118
+
Vector([0, 1]),
119
119
+
Vector([0, 2]),
120
120
+
Vector([0, 3]),
121
121
+
Vector([0, 4]),
122
122
+
Vector([0, 7]),
123
123
+
Vector([0, 9]),
124
124
+
Vector([1, 1]),
125
125
+
Vector([1, 2]),
126
126
+
Vector([1, 4]),
127
127
+
Vector([1, 5]),
128
128
+
Vector([1, 6]),
129
129
+
Vector([1, 8]),
130
130
+
Vector([2, 0]),
131
131
+
Vector([2, 1]),
132
132
+
Vector([2, 2]),
133
133
+
Vector([2, 3]),
134
134
+
Vector([2, 5]),
135
135
+
Vector([2, 7]),
136
136
+
Vector([2, 8]),
137
137
+
Vector([2, 9]),
138
138
+
Vector([3, 0]),
139
139
+
Vector([3, 2]),
140
140
+
Vector([3, 3]),
141
141
+
Vector([3, 4]),
142
142
+
Vector([3, 5]),
143
143
+
Vector([3, 6]),
144
144
+
Vector([3, 7]),
145
145
+
Vector([3, 8]),
146
146
+
Vector([4, 1]),
147
147
+
Vector([4, 2]),
148
148
+
Vector([4, 3]),
149
149
+
Vector([4, 4]),
150
150
+
Vector([4, 5]),
151
151
+
Vector([4, 7]),
152
152
+
Vector([4, 8]),
153
153
+
Vector([4, 9]),
154
154
+
Vector([5, 0]),
155
155
+
Vector([5, 3]),
156
156
+
Vector([5, 4]),
157
157
+
Vector([5, 5]),
158
158
+
Vector([5, 6]),
159
159
+
Vector([5, 8]),
160
160
+
Vector([5, 9]),
161
161
+
Vector([6, 0]),
162
162
+
Vector([6, 1]),
163
163
+
Vector([6, 2]),
164
164
+
Vector([6, 4]),
165
165
+
Vector([6, 5]),
166
166
+
Vector([6, 7]),
167
167
+
Vector([6, 8]),
168
168
+
Vector([6, 9]),
169
169
+
Vector([7, 0]),
170
170
+
Vector([7, 5]),
171
171
+
Vector([7, 6]),
172
172
+
Vector([7, 7]),
173
173
+
Vector([7, 8]),
174
174
+
Vector([8, 0]),
175
175
+
Vector([8, 1]),
176
176
+
Vector([8, 2]),
177
177
+
Vector([8, 3]),
178
178
+
Vector([8, 4]),
179
179
+
Vector([8, 6]),
180
180
+
Vector([8, 7]),
181
181
+
Vector([8, 8]),
182
182
+
Vector([8, 9]),
183
183
+
Vector([9, 1]),
184
184
+
Vector([9, 2]),
185
185
+
Vector([9, 4]),
186
186
+
Vector([9, 5]),
187
187
+
Vector([9, 6]),
188
188
+
Vector([9, 7]),
189
189
+
];
190
190
+
191
191
+
#[test]
192
192
+
fn parse_example_roll_locations() {
193
193
+
itertools::assert_equal(
194
194
+
parse_roll_locations(EXAMPLE_INPUT).unwrap().iter().sorted(),
195
195
+
EXAMPLE_ROLL_LOCATIONS,
196
196
+
);
197
197
+
}
198
198
+
199
199
+
#[test]
200
200
+
fn accessible_rolls_in_example() {
201
201
+
assert_eq!(
202
202
+
HashSet::from_iter(accessible_roll_locations(
203
203
+
&parse_roll_locations(EXAMPLE_INPUT).unwrap()
204
204
+
)),
205
205
+
parse_roll_locations(EXAMPLE_ACCESSIBLE_ROLLS).unwrap()
206
206
+
);
207
207
+
}
208
208
+
209
209
+
#[test]
210
210
+
fn non_removable_rolls_in_example() {
211
211
+
assert_eq!(
212
212
+
non_removable_roll_locations(parse_roll_locations(EXAMPLE_INPUT).unwrap()),
213
213
+
parse_roll_locations(EXAMPLE_NON_REMOVABLE_ROLLS).unwrap()
214
214
+
)
215
215
+
}
216
216
+
}
+2
-2
aoc_2025/src/main.rs
···
3
3
mod day01;
4
4
mod day02;
5
5
mod day03;
6
6
-
// mod day04;
6
6
+
mod day04;
7
7
// mod day05;
8
8
// mod day06;
9
9
// mod day07;
···
21
21
door!(2025-12-01 ~> day01),
22
22
door!(2025-12-02 ~> day02),
23
23
door!(2025-12-03 ~> day03),
24
24
-
// door!(2025-12-04 ~> day04),
24
24
+
door!(2025-12-04 ~> day04),
25
25
// door!(2025-12-05 ~> day05),
26
26
// door!(2025-12-06 ~> day06),
27
27
// door!(2025-12-07 ~> day07),