Skip to content

Commit 43af32c

Browse files
committed
Generalize 2024 day 18 to allow solving the example
1 parent 5b782c0 commit 43af32c

File tree

2 files changed

+93
-59
lines changed

2 files changed

+93
-59
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
5,4
2+
4,2
3+
4,5
4+
3,0
5+
2,1
6+
6,3
7+
2,4
8+
1,5
9+
0,6
10+
3,3
11+
2,6
12+
5,1
13+
1,2
14+
5,5
15+
2,5
16+
6,5
17+
1,4
18+
0,4
19+
6,4
20+
1,1
21+
6,1
22+
1,0
23+
0,5
24+
1,6
25+
2,0

crates/year2024/src/day18.rs

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,85 +6,96 @@ use utils::prelude::*;
66
/// Finding when the path through a grid is blocked.
77
#[derive(Clone, Debug)]
88
pub struct Day18 {
9-
blocked_at: [[u16; WIDTH]; WIDTH],
10-
fallen: u16,
9+
blocked_at: Vec<u16>,
10+
size: usize,
11+
start_idx: usize,
12+
end_idx: usize,
13+
part1_fallen: u16,
14+
total_fallen: u16,
1115
}
1216

13-
const MAX_COORD: usize = 70;
14-
const WIDTH: usize = MAX_COORD + 1;
15-
1617
impl Day18 {
17-
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
18-
let mut blocked_at = [[u16::MAX; WIDTH]; WIDTH];
18+
pub fn new(input: &str, input_type: InputType) -> Result<Self, InputError> {
19+
let (max_coord, part1_fallen) = match input_type {
20+
InputType::Example => (6, 12),
21+
InputType::Real => (70, 1024),
22+
};
23+
// +1 as max_coord is inclusive, +2 for padding on each side
24+
let size = max_coord + 3;
25+
26+
let mut blocked_at = vec![u16::MAX; size * size];
27+
for i in 0..size {
28+
blocked_at[i] = 0; // Top
29+
blocked_at[(size - 1) * size + i] = 0; // Bottom
30+
blocked_at[i * size] = 0; // Left
31+
blocked_at[i * size + (size - 1)] = 0; // Right
32+
}
33+
1934
let mut fallen = 0;
20-
for item in parser::number_range(0..=MAX_COORD)
21-
.then(parser::number_range(0..=MAX_COORD).with_prefix(b','))
35+
for item in parser::number_range(0..=max_coord)
36+
.then(parser::number_range(0..=max_coord).with_prefix(b','))
2237
.map(|(x, y)| Point2D { x, y })
2338
.with_suffix(parser::eol())
2439
.parse_iterator(input)
2540
{
2641
let pos = item?;
27-
if blocked_at[pos.x][pos.y] == u16::MAX {
28-
blocked_at[pos.x][pos.y] = fallen;
42+
if blocked_at[(pos.y + 1) * size + (pos.x + 1)] == u16::MAX {
43+
blocked_at[(pos.y + 1) * size + (pos.x + 1)] = fallen;
2944
fallen += 1;
3045
} else {
3146
return Err(InputError::new(input, 0, "duplicate position in input"));
3247
}
3348
}
3449

35-
Ok(Self { blocked_at, fallen })
50+
Ok(Self {
51+
blocked_at,
52+
size,
53+
start_idx: size + 1,
54+
end_idx: (max_coord + 1) * size + (max_coord + 1),
55+
part1_fallen,
56+
total_fallen: fallen,
57+
})
3658
}
3759

3860
#[must_use]
3961
pub fn part1(&self) -> u32 {
40-
self.minimum_steps(1024).expect("no solution found")
41-
}
42-
43-
fn minimum_steps(&self, fallen: u16) -> Option<u32> {
44-
let mut blocked_at = self.blocked_at;
62+
let mut blocked_at = self.blocked_at.clone();
4563
let mut queue = VecDeque::new();
46-
queue.push_back((Point2D::new(0, 0), 0));
64+
queue.push_back((self.start_idx, 0));
4765

4866
while let Some((pos, steps)) = queue.pop_front() {
49-
if pos == Point2D::new(MAX_COORD, MAX_COORD) {
50-
return Some(steps);
67+
if pos == self.end_idx {
68+
return steps;
5169
}
5270

53-
if pos.x > 0 && blocked_at[pos.x - 1][pos.y] >= fallen {
54-
queue.push_back((Point2D::new(pos.x - 1, pos.y), steps + 1));
55-
blocked_at[pos.x - 1][pos.y] = 0;
56-
}
57-
if pos.x < MAX_COORD && blocked_at[pos.x + 1][pos.y] >= fallen {
58-
queue.push_back((Point2D::new(pos.x + 1, pos.y), steps + 1));
59-
blocked_at[pos.x + 1][pos.y] = 0;
60-
}
61-
if pos.y > 0 && blocked_at[pos.x][pos.y - 1] >= fallen {
62-
queue.push_back((Point2D::new(pos.x, pos.y - 1), steps + 1));
63-
blocked_at[pos.x][pos.y - 1] = 0;
64-
}
65-
if pos.y < MAX_COORD && blocked_at[pos.x][pos.y + 1] >= fallen {
66-
queue.push_back((Point2D::new(pos.x, pos.y + 1), steps + 1));
67-
blocked_at[pos.x][pos.y + 1] = 0;
71+
for next in [pos - self.size, pos + 1, pos + self.size, pos - 1] {
72+
if blocked_at[next] >= self.part1_fallen {
73+
queue.push_back((next, steps + 1));
74+
blocked_at[next] = 0;
75+
}
6876
}
6977
}
7078

71-
None
79+
panic!("no solution found")
7280
}
7381

7482
#[must_use]
7583
pub fn part2(&self) -> String {
76-
let mut blocked_at = self.blocked_at;
77-
let mut reachable = vec![None; self.fallen as usize];
78-
let mut fallen = self.fallen;
79-
let mut next = Point2D::new(0, 0);
84+
let mut blocked_at = self.blocked_at.clone();
85+
let mut reachable = vec![None; self.total_fallen as usize];
86+
let mut fallen = self.total_fallen;
87+
let mut next = self.start_idx;
8088
loop {
8189
// Recursively flood fill the reachable grid spaces, tracking which grid spaces would
8290
// be reachable at a lower number of fallen bytes.
83-
if Self::fill(next, fallen, &mut blocked_at, &mut reachable).is_break() {
84-
if next == Point2D::new(0, 0) {
91+
if self
92+
.fill(next, fallen, &mut blocked_at, &mut reachable)
93+
.is_break()
94+
{
95+
if fallen == self.total_fallen {
8596
panic!("path is never blocked");
8697
}
87-
return format!("{},{}", next.x, next.y);
98+
return format!("{},{}", (next % self.size) - 1, (next / self.size) - 1);
8899
}
89100

90101
// No path to the end. Decrease fallen to the value blocking the highest reachable grid
@@ -98,31 +109,29 @@ impl Day18 {
98109
}
99110

100111
fn fill(
101-
pos: Point2D<usize>,
112+
&self,
113+
pos: usize,
102114
fallen: u16,
103-
blocked_at: &mut [[u16; 71]; 71],
104-
reachable: &mut [Option<Point2D<usize>>],
115+
blocked_at: &mut [u16],
116+
reachable: &mut [Option<usize>],
105117
) -> ControlFlow<()> {
106-
if pos == Point2D::new(MAX_COORD, MAX_COORD) {
118+
if pos == self.end_idx {
107119
return ControlFlow::Break(());
108120
}
109121

110-
for dir in [Point2D::UP, Point2D::RIGHT, Point2D::DOWN, Point2D::LEFT] {
111-
let next = pos.wrapping_add_signed(dir);
112-
if next.x > MAX_COORD || next.y > MAX_COORD {
113-
continue;
114-
}
115-
116-
if blocked_at[next.x][next.y] >= fallen {
117-
blocked_at[next.x][next.y] = 0;
118-
Self::fill(next, fallen, blocked_at, reachable)?;
119-
} else if blocked_at[next.x][next.y] > 0 {
120-
reachable[blocked_at[next.x][next.y] as usize] = Some(next);
122+
for next in [pos - self.size, pos + 1, pos + self.size, pos - 1] {
123+
if blocked_at[next] >= fallen {
124+
blocked_at[next] = 0;
125+
self.fill(next, fallen, blocked_at, reachable)?;
126+
} else if blocked_at[next] > 0 {
127+
reachable[blocked_at[next] as usize] = Some(next);
121128
}
122129
}
123130

124131
ControlFlow::Continue(())
125132
}
126133
}
127134

128-
examples!(Day18 -> (u32, &'static str) []);
135+
examples!(Day18 -> (u32, &'static str) [
136+
{file: "day18_example0.txt", part1: 22, part2: "6,1"},
137+
]);

0 commit comments

Comments
 (0)