day16: part 1 solution
All checks were successful
test / AoC 2024 (push) Successful in 2m56s

This commit is contained in:
Keenan Tims 2024-12-15 22:06:12 -08:00
parent f2186d18d3
commit 6283ff37f9
Signed by: ktims
GPG Key ID: 11230674D69038D4
5 changed files with 189 additions and 4 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -1,6 +1,6 @@
<!-- AOC TILES BEGIN --> <!-- AOC TILES BEGIN -->
<h1 align="center"> <h1 align="center">
2024 - 30 ⭐ - Rust 2024 - 31 ⭐ - Rust
</h1> </h1>
<a href="src/day1.rs"> <a href="src/day1.rs">
<img src=".aoc_tiles/tiles/2024/01.png" width="161px"> <img src=".aoc_tiles/tiles/2024/01.png" width="161px">
@ -47,4 +47,7 @@
<a href="src/day15.rs"> <a href="src/day15.rs">
<img src=".aoc_tiles/tiles/2024/15.png" width="161px"> <img src=".aoc_tiles/tiles/2024/15.png" width="161px">
</a> </a>
<a href="src/day16.rs">
<img src=".aoc_tiles/tiles/2024/16.png" width="161px">
</a>
<!-- AOC TILES END --> <!-- AOC TILES END -->

181
src/day16.rs Normal file
View File

@ -0,0 +1,181 @@
use aoc_runner_derive::{aoc, aoc_generator};
use grid::{AsCoord2d, Coord2d, Grid};
use std::{
collections::{BinaryHeap, HashMap},
str::FromStr,
};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
enum FacingDirection {
East,
South,
West,
North,
}
impl FacingDirection {
fn ofs(&self) -> (i64, i64) {
match self {
FacingDirection::East => (1, 0),
FacingDirection::South => (0, 1),
FacingDirection::West => (-1, 0),
FacingDirection::North => (0, -1),
}
}
fn reachable(&self) -> [FacingDirection; 3] {
// Can move perpendicularly or the same direction, not backwards
match self {
FacingDirection::East | FacingDirection::West => [*self, FacingDirection::North, FacingDirection::South],
FacingDirection::South | FacingDirection::North => [*self, FacingDirection::East, FacingDirection::West],
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
struct State {
cost: usize,
position: Coord2d,
facing: FacingDirection,
}
impl Ord for State {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other
.cost
.cmp(&self.cost)
.then_with(|| self.position.cmp(&other.position))
.then_with(|| self.facing.cmp(&other.facing))
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
struct Maze {
map: Grid<u8>,
}
impl FromStr for Maze {
type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let map: Grid<u8> = s.parse()?;
Ok(Self { map })
}
}
impl Maze {
fn dijkstra(&mut self) -> usize {
let start = self.map.find(&b'S').expect("can't find start").to_coord();
let finish = self.map.find(&b'E').expect("can't find finish").to_coord();
let mut distances = HashMap::new();
let mut queue = BinaryHeap::with_capacity(self.map.data.len());
distances.insert((start, FacingDirection::East), 0);
queue.push(State {
cost: 0,
position: start,
facing: FacingDirection::East,
});
while let Some(State { cost, position, facing }) = queue.pop() {
if position == finish {
return cost;
}
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) {
continue;
}
for (new_dir, new_position, new_cost) in facing
.reachable()
.iter()
.map(|dir| (dir, &position + dir.ofs()))
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#'))
.map(|(dir, pos)| (dir, pos, if *dir == facing { cost + 1 } else { cost + 1001 }))
{
if distances
.get(&(new_position, *new_dir))
.is_none_or(|best_cost| new_cost < *best_cost)
{
queue.push(State {
cost: new_cost,
position: new_position,
facing: *new_dir,
});
distances.insert((new_position, *new_dir), new_cost);
}
}
}
panic!("no path found");
}
}
fn parse(input: &str) -> Maze {
input.parse().unwrap()
}
#[aoc(day16, part1)]
pub fn part1(input: &str) -> usize {
let mut maze = parse(input);
maze.dijkstra()
}
#[aoc(day16, part2)]
pub fn part2(input: &str) -> i64 {
todo!()
}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE1: &str = "###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############";
const EXAMPLE2: &str = "#################
#...#...#...#..E#
#.#.#.#.#.#.#.#.#
#.#.#.#...#...#.#
#.#.#.#.###.#.#.#
#...#.#.#.....#.#
#.#.#.#.#.#####.#
#.#...#.#.#.....#
#.#.#####.#.###.#
#.#.#.......#...#
#.#.###.#####.###
#.#.#...#.....#.#
#.#.#.#####.###.#
#.#.#.........#.#
#.#.#.#########.#
#S#.............#
#################";
#[test]
fn part1_example() {
assert_eq!(part1(EXAMPLE1), 7036);
assert_eq!(part1(EXAMPLE2), 11048);
}
#[test]
fn part2_example() {
assert_eq!(part2(EXAMPLE1), 0);
assert_eq!(part2(EXAMPLE2), 0);
}
}

View File

@ -1,3 +1,4 @@
mod day16;
use aoc_runner_derive::aoc_lib; use aoc_runner_derive::aoc_lib;
pub mod day1; pub mod day1;

View File

@ -7,7 +7,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
#[derive(Clone, Debug)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Coord2d { pub struct Coord2d {
pub x: i64, pub x: i64,
pub y: i64, pub y: i64,
@ -126,7 +126,7 @@ pub struct Grid<T> {
width: i64, width: i64,
} }
impl<T: Clone + Eq + PartialEq + Display + Debug> Grid<T> { impl<T: Clone + Eq + PartialEq + Debug> Grid<T> {
pub fn new(width: i64) -> Self { pub fn new(width: i64) -> Self {
Self { Self {
data: Vec::new(), data: Vec::new(),
@ -134,7 +134,7 @@ impl<T: Clone + Eq + PartialEq + Display + Debug> Grid<T> {
} }
} }
/// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill` /// Returns a new [Grid] with the same shape (width x height) as `self`, filled with `fill`
pub fn same_shape<NT: Clone + Eq + PartialEq + Display + Debug>(&self, fill: NT) -> Grid<NT> { pub fn same_shape<NT: Clone + Eq + PartialEq + Debug>(&self, fill: NT) -> Grid<NT> {
Grid { Grid {
data: Vec::from_iter(repeat(fill).take(self.width() * self.height())), data: Vec::from_iter(repeat(fill).take(self.width() * self.height())),
width: self.width, width: self.width,