From b3bf7afaf2102d9d5cca64f6ac8e03a453f00c5e Mon Sep 17 00:00:00 2001 From: maneatingape <44142177+maneatingape@users.noreply.github.com> Date: Sat, 14 Sep 2024 00:03:07 +0200 Subject: [PATCH] Simplify cost checks and swap left/right up/down order --- README.md | 6 +- src/year2023/day17.rs | 165 ++++++++++++++++++++---------------------- 2 files changed, 83 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 5c32d9d..d9fd4af 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 | @@ -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 | diff --git a/src/year2023/day17.rs b/src/year2023/day17.rs index 506a226..46ac661 100644 --- a/src/year2023/day17.rs +++ b/src/year2023/day17.rs @@ -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 { +pub fn parse(input: &str) -> Grid { 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 { +pub fn part1(grid: &Grid) -> 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 { +pub fn part2(grid: &Grid) -> i32 { astar::<4, 10>(grid) } /// Optimized A* search. -fn astar(grid: &Grid) -> u32 { - let width = grid.width as usize; - let height = grid.height as usize; +fn astar(grid: &Grid) -> 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::>(); - 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; } @@ -83,90 +85,83 @@ fn astar(grid: &Grid) -> 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; } } }