Skip to content

Commit

Permalink
Simplify cost checks and swap left/right up/down order
Browse files Browse the repository at this point in the history
  • Loading branch information
maneatingape committed Sep 15, 2024
1 parent 28b35cb commit b3bf7af
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 88 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Place input files in `input/yearYYYY/dayDD.txt` including leading zeroes. For ex
## Performance

Benchmarks are measured using the built-in `cargo bench` tool run on an [Apple M2 Max][apple-link].
All 225 solutions from 2023 to 2015 complete sequentially in **581 milliseconds**.
All 225 solutions from 2023 to 2015 complete sequentially in **580 milliseconds**.
Interestingly 84% of the total time is spent on just 9 solutions.
Performance is reasonable even on older hardware, for example a 2011 MacBook Pro with an
[Intel i7-2720QM][intel-link] processor takes 3.5 seconds to run the same 225 solutions.
Expand All @@ -62,7 +62,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro

| Year | Benchmark (ms) |
| --- | --: |
| [2023](#2023) | 7 |
| [2023](#2023) | 6 |
| [2022](#2022) | 9 |
| [2021](#2021) | 9 |
| [2020](#2020) | 272 |
Expand Down Expand Up @@ -94,7 +94,7 @@ Performance is reasonable even on older hardware, for example a 2011 MacBook Pro
| 14 | [Parabolic Reflector Dish](https://adventofcode.com/2023/day/14) | [Source](src/year2023/day14.rs) | 632 |
| 15 | [Lens Library](https://adventofcode.com/2023/day/15) | [Source](src/year2023/day15.rs) | 84 |
| 16 | [The Floor Will Be Lava](https://adventofcode.com/2023/day/16) | [Source](src/year2023/day16.rs) | 826 |
| 17 | [Clumsy Crucible](https://adventofcode.com/2023/day/17) | [Source](src/year2023/day17.rs) | 2568 |
| 17 | [Clumsy Crucible](https://adventofcode.com/2023/day/17) | [Source](src/year2023/day17.rs) | 2270 |
| 18 | [Lavaduct Lagoon](https://adventofcode.com/2023/day/18) | [Source](src/year2023/day18.rs) | 17 |
| 19 | [Aplenty](https://adventofcode.com/2023/day/19) | [Source](src/year2023/day19.rs) | 100 |
| 20 | [Pulse Propagation](https://adventofcode.com/2023/day/20) | [Source](src/year2023/day20.rs) | 6 |
Expand Down
165 changes: 80 additions & 85 deletions src/year2023/day17.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,51 +30,53 @@ use crate::util::grid::*;
use crate::util::parse::*;

/// Parse the input into a 2D grid of `u8` then convert to `u32` for convenience.
pub fn parse(input: &str) -> Grid<u32> {
pub fn parse(input: &str) -> Grid<i32> {
let Grid { width, height, bytes } = Grid::parse(input);
let bytes = bytes.iter().map(|b| b.to_decimal() as u32).collect();
let bytes = bytes.iter().map(|b| b.to_decimal() as i32).collect();
Grid { width, height, bytes }
}

/// Search with a maximum of 3 steps in any direction.
pub fn part1(grid: &Grid<u32>) -> u32 {
pub fn part1(grid: &Grid<i32>) -> i32 {
astar::<1, 3>(grid)
}

/// Search with a minimum of 4 and maximum of 10 steps in any direction. Using const generics
/// to specify the limits allows the compiler to optimize and unroll loops, speeding things
/// up by about 25%, versus specifying the loop limits as regular parameters.
pub fn part2(grid: &Grid<u32>) -> u32 {
pub fn part2(grid: &Grid<i32>) -> i32 {
astar::<4, 10>(grid)
}

/// Optimized A* search.
fn astar<const L: usize, const U: usize>(grid: &Grid<u32>) -> u32 {
let width = grid.width as usize;
let height = grid.height as usize;
fn astar<const L: i32, const U: i32>(grid: &Grid<i32>) -> i32 {
let size = grid.width;
let stride = size as usize;
let heat = &grid.bytes;

let mut index = 0;
let mut todo = (0..100).map(|_| Vec::with_capacity(1000)).collect::<Vec<_>>();
let mut cost = vec![[0_u32; 2]; heat.len()];
let mut cost = vec![[i32::MAX; 2]; heat.len()];

// Start from the top left corner checking both vertical and horizontal directions.
todo[0].push((0, 0, 0));
todo[0].push((0, 0, 1));

cost[0][0] = 0;
cost[0][1] = 0;

loop {
// All items in the same bucket have the same priority.
while let Some((x, y, direction)) = todo[index % 100].pop() {
// Retrieve cost for our current location and direction.
let index = width * y + x;
let index = (size * y + x) as usize;
let steps = cost[index][direction];

// The heuristic is used as an index into the bucket priority queue.
let heuristic =
|x: usize, y: usize, cost: u32| (cost as usize + width - x + height - y) % 100;
let heuristic = |x: i32, y: i32, cost: i32| ((cost + 2 * size - x - y) % 100) as usize;

// Check if we've reached the end.
if x == width - 1 && y == height - 1 {
if x == size - 1 && y == size - 1 {
return steps;
}

Expand All @@ -83,90 +85,83 @@ fn astar<const L: usize, const U: usize>(grid: &Grid<u32>) -> u32 {
if direction == 0 {
// We just moved vertically so now check both left and right directions.

// Left
{
let mut index = index;
let mut steps = steps;

// Each direction loop is the same:
// * Check to see if we gone out of bounds
// * Increase the cost by the "heat" of the square we've just moved into.
// * Check if we've already been to this location with a lower cost.
// * Add new state to priority queue.
for i in 1..=U {
if i > x {
break;
}

index -= 1;
steps += heat[index];

if i >= L && (cost[index][1] == 0 || steps < cost[index][1]) {
todo[heuristic(x - i, y, steps)].push((x - i, y, 1));
cost[index][1] = steps;
}
// Each direction loop is the same:
// * Check to see if we gone out of bounds
// * Increase the cost by the "heat" of the square we've just moved into.
// * Check if we've already been to this location with a lower cost.
// * Add new state to priority queue.

// Right
let mut next = index;
let mut extra = steps;

for i in 1..=U {
if x + i >= size {
break;
}

next += 1;
extra += heat[next];

if i >= L && extra < cost[next][1] {
todo[heuristic(x + i, y, extra)].push((x + i, y, 1));
cost[next][1] = extra;
}
}

// Right
{
let mut index = index;
let mut steps = steps;

for i in 1..=U {
if x + i >= width {
break;
}

index += 1;
steps += heat[index];

if i >= L && (cost[index][1] == 0 || steps < cost[index][1]) {
todo[heuristic(x + i, y, steps)].push((x + i, y, 1));
cost[index][1] = steps;
}
// Left
let mut next = index;
let mut extra = steps;

for i in 1..=U {
if i > x {
break;
}

next -= 1;
extra += heat[next];

if i >= L && extra < cost[next][1] {
todo[heuristic(x - i, y, extra)].push((x - i, y, 1));
cost[next][1] = extra;
}
}
} else {
// We just moved horizontally so now check both up and down directions.

// Up
{
let mut index = index;
let mut steps = steps;

for i in 1..=U {
if i > y {
break;
}

index -= width;
steps += heat[index];

if i >= L && (cost[index][0] == 0 || steps < cost[index][0]) {
todo[heuristic(x, y - i, steps)].push((x, y - i, 0));
cost[index][0] = steps;
}
// Down
let mut next = index;
let mut extra = steps;

for i in 1..=U {
if y + i >= size {
break;
}

next += stride;
extra += heat[next];

if i >= L && extra < cost[next][0] {
todo[heuristic(x, y + i, extra)].push((x, y + i, 0));
cost[next][0] = extra;
}
}

// Down
{
let mut index = index;
let mut steps = steps;

for i in 1..=U {
if y + i >= height {
break;
}

index += width;
steps += heat[index];

if i >= L && (cost[index][0] == 0 || steps < cost[index][0]) {
todo[heuristic(x, y + i, steps)].push((x, y + i, 0));
cost[index][0] = steps;
}
// Up
let mut next = index;
let mut extra = steps;

for i in 1..=U {
if i > y {
break;
}

next -= stride;
extra += heat[next];

if i >= L && extra < cost[next][0] {
todo[heuristic(x, y - i, extra)].push((x, y - i, 0));
cost[next][0] = extra;
}
}
}
Expand Down

0 comments on commit b3bf7af

Please sign in to comment.