Skip to content

Commit 5b782c0

Browse files
committed
Optimize 2024 day 18
1 parent 4811b51 commit 5b782c0

File tree

2 files changed

+101
-46
lines changed

2 files changed

+101
-46
lines changed

crates/utils/src/parser/number.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ macro_rules! parser_for {
9292
parser_for! { UnsignedParser => u8, u16, u32, u64, u128 }
9393
parser_for! { SignedParser => i8, i16, i32, i64, i128 }
9494

95+
/// Parsing as [`usize`] should be discouraged as it leads to parsers which behave differently at
96+
/// runtime on 32-bit and 64-bit platforms, so no `parser::usize()` function is provided.
97+
///
98+
/// However, [`Parseable`] is implemented for [`usize`] as it is safe to use [`number_range()`]
99+
/// with a constant hard-coded max, which will fail at compile time if the constant is too large
100+
/// for the platform's usize.
101+
impl Parseable for std::primitive::usize {
102+
type Parser = UnsignedParser<std::primitive::usize>;
103+
const PARSER: Self::Parser = UnsignedParser(PhantomData);
104+
}
105+
95106
#[derive(Copy, Clone)]
96107
pub struct NumberRange<I> {
97108
min: I,

crates/year2024/src/day18.rs

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,128 @@
11
use std::collections::VecDeque;
2+
use std::ops::ControlFlow;
23
use utils::point::Point2D;
34
use utils::prelude::*;
45

5-
/// Finding when a path through a grid is blocked.
6+
/// Finding when the path through a grid is blocked.
67
#[derive(Clone, Debug)]
78
pub struct Day18 {
8-
positions: Vec<Point2D<usize>>,
9+
blocked_at: [[u16; WIDTH]; WIDTH],
10+
fallen: u16,
911
}
1012

13+
const MAX_COORD: usize = 70;
14+
const WIDTH: usize = MAX_COORD + 1;
15+
1116
impl Day18 {
1217
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
13-
Ok(Self {
14-
positions: parser::number_range(0u32..=70)
15-
.repeat_n(",")
16-
.map(|[x, y]| Point2D {
17-
x: x as usize,
18-
y: y as usize,
19-
})
20-
.parse_lines(input)?,
21-
})
18+
let mut blocked_at = [[u16::MAX; WIDTH]; WIDTH];
19+
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','))
22+
.map(|(x, y)| Point2D { x, y })
23+
.with_suffix(parser::eol())
24+
.parse_iterator(input)
25+
{
26+
let pos = item?;
27+
if blocked_at[pos.x][pos.y] == u16::MAX {
28+
blocked_at[pos.x][pos.y] = fallen;
29+
fallen += 1;
30+
} else {
31+
return Err(InputError::new(input, 0, "duplicate position in input"));
32+
}
33+
}
34+
35+
Ok(Self { blocked_at, fallen })
2236
}
2337

2438
#[must_use]
2539
pub fn part1(&self) -> u32 {
2640
self.minimum_steps(1024).expect("no solution found")
2741
}
2842

29-
#[must_use]
30-
pub fn part2(&self) -> String {
31-
let mut left = 0;
32-
let mut right = self.positions.len();
33-
34-
while left < right {
35-
let mid = (left + right) / 2;
36-
match self.minimum_steps(mid + 1) {
37-
Some(_) => left = mid + 1,
38-
None => right = mid,
39-
}
40-
}
41-
42-
if left >= self.positions.len() {
43-
panic!("no solution found");
44-
}
45-
46-
format!("{},{}", self.positions[left].x, self.positions[left].y)
47-
}
48-
49-
fn minimum_steps(&self, fallen: usize) -> Option<u32> {
50-
let mut blocked = [[false; 71]; 71];
51-
for pos in self.positions.iter().take(fallen) {
52-
blocked[pos.x][pos.y] = true;
53-
}
54-
43+
fn minimum_steps(&self, fallen: u16) -> Option<u32> {
44+
let mut blocked_at = self.blocked_at;
5545
let mut queue = VecDeque::new();
5646
queue.push_back((Point2D::new(0, 0), 0));
5747

5848
while let Some((pos, steps)) = queue.pop_front() {
59-
if pos == Point2D::new(70, 70) {
49+
if pos == Point2D::new(MAX_COORD, MAX_COORD) {
6050
return Some(steps);
6151
}
62-
if pos.x > 0 && !blocked[pos.x - 1][pos.y] {
52+
53+
if pos.x > 0 && blocked_at[pos.x - 1][pos.y] >= fallen {
6354
queue.push_back((Point2D::new(pos.x - 1, pos.y), steps + 1));
64-
blocked[pos.x - 1][pos.y] = true;
55+
blocked_at[pos.x - 1][pos.y] = 0;
6556
}
66-
if pos.x < 70 && !blocked[pos.x + 1][pos.y] {
57+
if pos.x < MAX_COORD && blocked_at[pos.x + 1][pos.y] >= fallen {
6758
queue.push_back((Point2D::new(pos.x + 1, pos.y), steps + 1));
68-
blocked[pos.x + 1][pos.y] = true;
59+
blocked_at[pos.x + 1][pos.y] = 0;
6960
}
70-
if pos.y > 0 && !blocked[pos.x][pos.y - 1] {
61+
if pos.y > 0 && blocked_at[pos.x][pos.y - 1] >= fallen {
7162
queue.push_back((Point2D::new(pos.x, pos.y - 1), steps + 1));
72-
blocked[pos.x][pos.y - 1] = true;
63+
blocked_at[pos.x][pos.y - 1] = 0;
7364
}
74-
if pos.y < 70 && !blocked[pos.x][pos.y + 1] {
65+
if pos.y < MAX_COORD && blocked_at[pos.x][pos.y + 1] >= fallen {
7566
queue.push_back((Point2D::new(pos.x, pos.y + 1), steps + 1));
76-
blocked[pos.x][pos.y + 1] = true;
67+
blocked_at[pos.x][pos.y + 1] = 0;
7768
}
7869
}
7970

8071
None
8172
}
73+
74+
#[must_use]
75+
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);
80+
loop {
81+
// Recursively flood fill the reachable grid spaces, tracking which grid spaces would
82+
// 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) {
85+
panic!("path is never blocked");
86+
}
87+
return format!("{},{}", next.x, next.y);
88+
}
89+
90+
// No path to the end. Decrease fallen to the value blocking the highest reachable grid
91+
// space and try again.
92+
fallen = reachable[..fallen as usize]
93+
.iter()
94+
.rposition(Option::is_some)
95+
.expect("no solution found") as u16;
96+
next = reachable[fallen as usize].unwrap();
97+
}
98+
}
99+
100+
fn fill(
101+
pos: Point2D<usize>,
102+
fallen: u16,
103+
blocked_at: &mut [[u16; 71]; 71],
104+
reachable: &mut [Option<Point2D<usize>>],
105+
) -> ControlFlow<()> {
106+
if pos == Point2D::new(MAX_COORD, MAX_COORD) {
107+
return ControlFlow::Break(());
108+
}
109+
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);
121+
}
122+
}
123+
124+
ControlFlow::Continue(())
125+
}
82126
}
83127

84128
examples!(Day18 -> (u32, &'static str) []);

0 commit comments

Comments
 (0)