Skip to content

Commit e5d1764

Browse files
committed
Optimize 2024 day 20
Rewriting to allow vectorization reduces runtime from ~15ms to ~4ms, or ~1ms with target_cpu=native.
1 parent 8c783a7 commit e5d1764

File tree

1 file changed

+48
-62
lines changed

1 file changed

+48
-62
lines changed

crates/year2024/src/day20.rs

Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use utils::prelude::*;
55
/// Finding shortcuts phasing through walls in a maze.
66
#[derive(Clone, Debug)]
77
pub struct Day20 {
8-
grid: Vec<u8>,
9-
distances: Vec<u32>,
8+
distances: Vec<Distance>,
109
cols: usize,
1110
}
1211

12+
// Depending on AVX support, u16 or u32 can be faster
13+
type Distance = u16;
14+
1315
impl Day20 {
1416
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
1517
let (_, cols, mut grid) = grid::from_str_padded(input, 20, b'#', |b| match b {
@@ -35,58 +37,46 @@ impl Day20 {
3537
}
3638
grid[end] = b'.';
3739

38-
let mut distances = vec![u32::MAX; grid.len()];
40+
let mut distances = vec![Distance::MAX; grid.len()];
3941
distances[start] = 0;
4042
let mut queue = VecDeque::new();
41-
queue.push_back((start, 0));
43+
queue.push_back((start, 0 as Distance));
4244
while let Some((index, distance)) = queue.pop_front() {
4345
if index == end {
4446
break;
4547
}
4648

49+
let Some(next_distance) = distance.checked_add(1) else {
50+
return Err(InputError::new(input, 0, "path too long"));
51+
};
52+
4753
for offset in [1, cols as isize, -1, -(cols as isize)] {
4854
let next = index.wrapping_add_signed(offset);
49-
if grid[next] == b'.' && distances[next] == u32::MAX {
50-
distances[next] = distance + 1;
51-
queue.push_back((next, distance + 1));
55+
if grid[next] == b'.' && distances[next] == Distance::MAX {
56+
distances[next] = next_distance;
57+
queue.push_back((next, next_distance));
5258
}
5359
}
5460
}
5561

56-
Ok(Self {
57-
grid,
58-
distances,
59-
cols,
60-
})
62+
Ok(Self { distances, cols })
6163
}
6264

6365
#[must_use]
6466
pub fn part1(&self) -> u32 {
6567
let mut cheats = 0;
66-
for index in (self.cols * 20)..self.grid.len() - (self.cols * 20) {
67-
let this_distance = self.distances[index];
68-
if this_distance == u32::MAX {
69-
continue;
70-
}
71-
72-
for offset in [1, self.cols as isize, -1, -(self.cols as isize)] {
73-
if self.grid[index.wrapping_add_signed(offset)] != b'#' {
74-
continue;
75-
}
76-
77-
let Some(next) = index.checked_add_signed(offset * 2) else {
78-
continue;
79-
};
80-
let Some(&target_distance) = self.distances.get(next) else {
81-
continue;
82-
};
83-
if target_distance == u32::MAX || target_distance < this_distance + 2 {
84-
continue;
85-
}
86-
let diff = target_distance - this_distance - 2;
87-
if diff >= 100 {
88-
cheats += 1;
89-
}
68+
for offset in [2, self.cols as isize * 2, -2, self.cols as isize * -2] {
69+
for (index, target) in (self.cols * 20..self.distances.len() - (self.cols * 20))
70+
.zip((self.cols * 20).wrapping_add_signed(offset)..)
71+
{
72+
let this_distance = self.distances[index];
73+
let target_distance = self.distances[target];
74+
cheats += u32::from(
75+
(target_distance != Distance::MAX)
76+
& (this_distance != Distance::MAX)
77+
& (target_distance > this_distance.wrapping_add(2))
78+
& (target_distance.wrapping_sub(this_distance).wrapping_sub(2) >= 100),
79+
);
9080
}
9181
}
9282
cheats
@@ -95,36 +85,32 @@ impl Day20 {
9585
#[must_use]
9686
pub fn part2(&self) -> u32 {
9787
let mut cheats = 0;
98-
for index in (self.cols * 20)..self.grid.len() - (self.cols * 20) {
99-
let this_distance = self.distances[index];
100-
if this_distance == u32::MAX {
101-
continue;
102-
}
103-
104-
for x_offset in -20isize..=20 {
105-
let y_limit = 20 - x_offset.abs();
106-
for y_offset in -y_limit..=y_limit {
107-
let cheat_length = (x_offset.unsigned_abs() + y_offset.unsigned_abs()) as u32;
108-
if cheat_length == 0 {
109-
continue;
110-
}
111-
112-
let offset = y_offset * (self.cols as isize) + x_offset;
113-
let next = index.wrapping_add_signed(offset);
114-
let target_distance = self.distances[next];
115-
if target_distance == u32::MAX || target_distance < this_distance + cheat_length
116-
{
117-
continue;
118-
}
88+
for x_offset in -20isize..=20 {
89+
let y_limit = 20 - x_offset.abs();
90+
for y_offset in -y_limit..=y_limit {
91+
let cheat_length = (x_offset.unsigned_abs() + y_offset.unsigned_abs()) as Distance;
92+
if cheat_length == 0 {
93+
continue;
94+
}
11995

120-
let diff = target_distance - this_distance - cheat_length;
121-
if diff >= 100 {
122-
cheats += 1;
123-
}
96+
let offset = y_offset * (self.cols as isize) + x_offset;
97+
for (index, target) in (self.cols * 20..self.distances.len() - (self.cols * 20))
98+
.zip((self.cols * 20).wrapping_add_signed(offset)..)
99+
{
100+
let this_distance = self.distances[index];
101+
let target_distance = self.distances[target];
102+
cheats += u32::from(
103+
(target_distance != Distance::MAX)
104+
& (this_distance != Distance::MAX)
105+
& (target_distance > this_distance.wrapping_add(cheat_length))
106+
& (target_distance
107+
.wrapping_sub(this_distance)
108+
.wrapping_sub(cheat_length)
109+
>= 100),
110+
);
124111
}
125112
}
126113
}
127-
128114
cheats
129115
}
130116
}

0 commit comments

Comments
 (0)