Skip to content

Commit 30b6ec3

Browse files
committed
Faster approach with incremental BFS
1 parent 15739f5 commit 30b6ec3

File tree

2 files changed

+63
-35
lines changed

2 files changed

+63
-35
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
8989
| 15 | [Warehouse Woes](https://adventofcode.com/2024/day/15) | [Source](src/year2024/day15.rs) | 303 |
9090
| 16 | [Reindeer Maze](https://adventofcode.com/2024/day/16) | [Source](src/year2024/day16.rs) | 390 |
9191
| 17 | [Chronospatial Computer](https://adventofcode.com/2024/day/17) | [Source](src/year2024/day17.rs) | 2 |
92-
| 18 | [RAM Run](https://adventofcode.com/2024/day/18) | [Source](src/year2024/day18.rs) | 61 |
92+
| 18 | [RAM Run](https://adventofcode.com/2024/day/18) | [Source](src/year2024/day18.rs) | 42 |
9393

9494
## 2023
9595

src/year2024/day18.rs

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,66 +14,94 @@
1414
//! ```
1515
//!
1616
//! Now we can [BFS](https://en.wikipedia.org/wiki/Breadth-first_search) from any arbitrary
17-
//! start time. Squares are blocked only if the grid time is less than or equal to the start time.
17+
//! start time. Squares are safe if the grid time is greater than the start time.
1818
//!
19-
//! A [binary search](https://en.wikipedia.org/wiki/Binary_search) is much faster than a
20-
//! linear search with complexity `O(log₂n)` vs `O(n)`. For example `log₂(3450) = 12`.
19+
//! Part two uses an incremental BFS, getting a little further each time and removing blocking
20+
//! bytes in descending order of time until we reach the exit.
21+
//!
22+
//! * Start with `t = i32::MAX`
23+
//! * Start BFS from top-left origin.
24+
//! * If we encounter a blocking byte with a time less than `t`
25+
//! then push `(time, position)` onto a max heap keyed by time.
26+
//! * If we exhaust the BFS `VecDeque` then pop the heap's top item. This is the oldest byte that
27+
//! we encountered blocking the way. Set `t` to the byte's time and push position to the dequeue.
28+
//! * Repeat BFS until we reach the exit.
2129
use crate::util::grid::*;
30+
use crate::util::heap::*;
2231
use crate::util::iter::*;
2332
use crate::util::parse::*;
2433
use crate::util::point::*;
2534
use std::collections::VecDeque;
2635

27-
pub fn parse(input: &str) -> Grid<u16> {
28-
let mut grid = Grid::new(71, 71, u16::MAX);
36+
pub fn parse(input: &str) -> Grid<i32> {
37+
let mut grid = Grid::new(71, 71, i32::MAX);
2938

3039
for (i, [x, y]) in input.iter_signed::<i32>().chunk::<2>().enumerate() {
31-
grid[Point::new(x, y)] = i as u16;
40+
grid[Point::new(x, y)] = i as i32;
3241
}
3342

3443
grid
3544
}
3645

37-
pub fn part1(grid: &Grid<u16>) -> u32 {
38-
bfs(grid, 1024).unwrap()
39-
}
40-
41-
pub fn part2(grid: &Grid<u16>) -> String {
42-
let mut lower = 0;
43-
let mut upper = 5041;
44-
45-
while lower < upper {
46-
let middle = (lower + upper) / 2;
47-
if bfs(grid, middle).is_some() {
48-
lower = middle + 1;
49-
} else {
50-
upper = middle;
51-
}
52-
}
53-
54-
let index = grid.bytes.iter().position(|&time| time == lower).unwrap() as i32;
55-
format!("{},{}", index % grid.width, index / grid.width)
56-
}
57-
58-
fn bfs(grid: &Grid<u16>, time: u16) -> Option<u32> {
46+
/// BFS from start to exit using a fixed time of 1024.
47+
pub fn part1(grid: &Grid<i32>) -> u32 {
48+
let mut grid = grid.clone();
5949
let mut todo = VecDeque::new();
60-
let mut seen = grid.clone();
6150

51+
grid[ORIGIN] = 0;
6252
todo.push_back((ORIGIN, 0));
63-
seen[ORIGIN] = 0;
6453

6554
while let Some((position, cost)) = todo.pop_front() {
6655
if position == Point::new(70, 70) {
67-
return Some(cost);
56+
return cost;
6857
}
6958

7059
for next in ORTHOGONAL.map(|o| position + o) {
71-
if seen.contains(next) && time < seen[next] {
60+
if grid.contains(next) && grid[next] > 1024 {
61+
grid[next] = 0;
7262
todo.push_back((next, cost + 1));
73-
seen[next] = 0;
7463
}
7564
}
7665
}
7766

78-
None
67+
unreachable!()
68+
}
69+
70+
/// Incremental BFS that removes one blocking byte at a time in descending order.
71+
pub fn part2(grid: &Grid<i32>) -> String {
72+
let mut time = i32::MAX;
73+
let mut heap = MinHeap::new();
74+
75+
let mut grid = grid.clone();
76+
let mut todo = VecDeque::new();
77+
78+
grid[ORIGIN] = 0;
79+
todo.push_back(ORIGIN);
80+
81+
loop {
82+
// Incremental BFS that makes as much progress as possible.
83+
while let Some(position) = todo.pop_front() {
84+
if position == Point::new(70, 70) {
85+
let index = grid.bytes.iter().position(|&b| b == time).unwrap() as i32;
86+
return format!("{},{}", index % grid.width, index / grid.width);
87+
}
88+
89+
for next in ORTHOGONAL.map(|o| position + o) {
90+
if grid.contains(next) {
91+
if time < grid[next] {
92+
grid[next] = 0;
93+
todo.push_back(next);
94+
} else {
95+
// Use negative value to convert min-heap to max-heap.
96+
heap.push(-grid[next], next);
97+
}
98+
}
99+
}
100+
}
101+
102+
// Remove the latest blocking byte then try to make a little more progress in BFS.
103+
let (first, saved) = heap.pop().unwrap();
104+
time = -first;
105+
todo.push_back(saved);
106+
}
79107
}

0 commit comments

Comments
 (0)