diff --git a/2023/Cargo.lock b/2023/Cargo.lock index 77020f6..055dce3 100644 --- a/2023/Cargo.lock +++ b/2023/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "aoc-runner", "aoc-runner-derive", "num", + "pathfinding", ] [[package]] @@ -70,6 +71,55 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "deprecate-until" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3767f826efbbe5a5ae093920b58b43b01734202be697e1354914e862e8e704" +dependencies = [ + "proc-macro2", + "quote", + "semver", + "syn 2.0.39", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "itoa" version = "1.0.9" @@ -164,6 +214,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "pathfinding" +version = "4.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f4a3f5089b981000cb50ec24320faf7a19649a45e8730e4adf49f78f066528" +dependencies = [ + "deprecate-until", + "fixedbitset", + "indexmap", + "integer-sqrt", + "num-traits", + "rustc-hash", + "thiserror", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -211,12 +276,24 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.193" @@ -272,18 +349,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", diff --git a/2023/Cargo.toml b/2023/Cargo.toml index 0f30677..f865997 100644 --- a/2023/Cargo.toml +++ b/2023/Cargo.toml @@ -12,6 +12,7 @@ aoc-runner = "0.3.0" aoc-runner-derive = "0.3.0" aoc-parse = "0.2.17" num = "0.4" +pathfinding = "4.8.0" [profile.release] incremental = true diff --git a/2023/src/day17.rs b/2023/src/day17.rs new file mode 100644 index 0000000..ffdf924 --- /dev/null +++ b/2023/src/day17.rs @@ -0,0 +1,135 @@ +use aoc_runner_derive::{aoc, aoc_generator}; +use pathfinding::matrix::{directions, Matrix}; +use pathfinding::prelude::astar; + +#[aoc_generator(day17)] +fn parse(input: &str) -> Matrix { + input + .lines() + .filter_map(|line| { + if !line.is_empty() { + Some( + line.chars() + .map(|c| c.to_digit(10).expect("bad digit") as usize), + ) + } else { + None + } + }) + .collect::>() +} + +#[derive(Clone, Eq, Hash, PartialEq, Ord, PartialOrd)] +struct Path { + position: (usize, usize), + dir: (isize, isize), + steps: usize, +} + +fn distance(s: &(usize, usize), d: &(usize, usize)) -> usize { + s.0.abs_diff(d.0) + s.1.abs_diff(d.1) +} + +fn find_path(map: &Matrix) -> usize { + let start = Path { + position: (0, 0), + dir: (0, 0), + steps: 0, + }; + let dest = (map.rows - 1, map.columns - 1); + + let path = astar( + &start, + |cur| match cur.steps >= MIN || (cur.dir.0 == 0 && cur.dir.1 == 0) { + true => find_successors::(map, cur), + false => continue_in_direction::(map, cur), + }, + |cur| distance(&dest, &cur.position), + |cur| cur.position == dest && cur.steps >= MIN, + ) + .expect("path is missing"); + path.1 +} + +fn find_successors(map: &Matrix, p: &Path) -> Vec<(Path, usize)> { + [directions::N, directions::E, directions::S, directions::W] + .iter() + .flat_map(|dir| { + map.move_in_direction(p.position, *dir) + .map(|cur| (cur, *dir, *map.get(cur).unwrap())) + }) + .filter(|(cur, dir, _weight)| { + let backwards = p.dir.0 == -dir.0 && p.dir.1 == -dir.1; + !backwards && *cur != (0, 0) + }) + .flat_map(|(cur, dir, weight)| { + let steps = match p.dir == dir { + true => p.steps + 1, + false => 1, + }; + + match steps <= MAX { + true => { + let next = Path { + position: cur, + dir, + steps, + }; + Some((next, weight)) + } + false => None, + } + }) + .collect::>() +} + +fn continue_in_direction(map: &Matrix, p: &Path) -> Vec<(Path, usize)> { + match map.move_in_direction(p.position, p.dir) { + Some(point) => { + let heat = *map.get(point).unwrap(); + let new_path = Path { + position: point, + dir: p.dir, + steps: p.steps + 1, + }; + vec![(new_path, heat)] + } + None => Vec::with_capacity(0), + } +} + +#[aoc(day17, part1)] +fn part1(input: &Matrix) -> usize { + find_path::<1, 3>(&input) +} + +#[aoc(day17, part2)] +fn part2(input: &Matrix) -> usize { + find_path::<4, 10>(&input) +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_DATA: &'static str = r#"2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 +"#; + + #[test] + fn sample_data() { + let input = parse(&SAMPLE_DATA); + assert_eq!(part1(&input), 102); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index bd848b8..1fdf8f6 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -16,5 +16,6 @@ mod day13; mod day14; mod day15; mod day16; +mod day17; aoc_lib! { year = 2023 }