Compare commits

..

No commits in common. "20e68895725ff23dbd2ee33b33db498b1a7fa61f" and "4b85a906353d6d3c1f36abf874f09c662d29ff57" have entirely different histories.

2 changed files with 98 additions and 127 deletions

View File

@ -1,14 +1,13 @@
use aoc_runner_derive::aoc; use aoc_runner_derive::aoc;
use grid::Grid; use grid::{AsCoord2d, Coord2d, Grid};
use itertools::Itertools;
use std::{ use std::{
collections::{BinaryHeap, HashMap}, collections::{BinaryHeap, HashMap},
i64,
str::FromStr, str::FromStr,
usize, usize,
}; };
type CoordType = i16;
type Coord = (CoordType, CoordType);
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
enum FacingDirection { enum FacingDirection {
East, East,
@ -18,7 +17,7 @@ enum FacingDirection {
} }
impl FacingDirection { impl FacingDirection {
fn ofs(&self) -> (CoordType, CoordType) { fn ofs(&self) -> (i64, i64) {
match self { match self {
FacingDirection::East => (1, 0), FacingDirection::East => (1, 0),
FacingDirection::South => (0, 1), FacingDirection::South => (0, 1),
@ -38,8 +37,9 @@ impl FacingDirection {
#[derive(Clone, Eq, PartialEq, Debug)] #[derive(Clone, Eq, PartialEq, Debug)]
struct State { struct State {
cost: usize, cost: usize,
position: Coord, position: Coord2d,
facing: FacingDirection, facing: FacingDirection,
path: Vec<Coord2d>,
} }
impl Ord for State { impl Ord for State {
@ -58,58 +58,63 @@ impl PartialOrd for State {
} }
} }
#[derive(Clone, Eq, PartialEq, Debug)]
struct PathState {
state: State,
path: Vec<Coord>,
}
impl Ord for PathState {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.state.cmp(&other.state)
}
}
impl PartialOrd for PathState {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.state.partial_cmp(&other.state)
}
}
struct Maze { struct Maze {
map: Grid<u8>, map: Grid<u8>,
paths: HashMap<usize, Vec<Vec<Coord2d>>>,
} }
impl FromStr for Maze { impl FromStr for Maze {
type Err = Box<dyn std::error::Error>; type Err = Box<dyn std::error::Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let map: Grid<u8> = s.parse()?; let map: Grid<u8> = s.parse()?;
let paths = HashMap::new();
Ok(Self { map }) Ok(Self { map, paths })
} }
} }
impl Maze { impl Maze {
fn dijkstra(&self) -> usize { fn dijkstra<const RECORD_PATHS: bool>(&mut self) -> usize {
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start"); let start = self.map.find(&b'S').expect("can't find start").to_coord();
let start = (start_x as CoordType, start_y as CoordType); let finish = self.map.find(&b'E').expect("can't find finish").to_coord();
let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish");
let finish = (finish_x as CoordType, finish_y as CoordType);
let mut distances = HashMap::new(); let mut distances = HashMap::new();
let mut queue = BinaryHeap::new(); let mut queue = BinaryHeap::with_capacity(self.map.data.len());
let mut best_cost = usize::MAX;
distances.insert((start, FacingDirection::East), 0); distances.insert((start, FacingDirection::East), 0);
queue.push(State { queue.push(State {
cost: 0, cost: 0,
position: start, position: start,
facing: FacingDirection::East, facing: FacingDirection::East,
path: Vec::new(),
}); });
while let Some(State { cost, position, facing }) = queue.pop() { while let Some(State {
cost,
position,
facing,
path,
}) = queue.pop()
{
let mut new_path = path.clone();
new_path.push(position);
if position == finish { if position == finish {
if RECORD_PATHS {
if cost < best_cost {
best_cost = cost
}
if !self.paths.contains_key(&cost) {
self.paths.insert(cost, vec![new_path.clone()]);
} else {
self.paths.get_mut(&cost).unwrap().push(new_path.clone());
}
continue;
} else {
return cost; return cost;
} }
}
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) { if distances.get(&(position, facing)).is_some_and(|v| cost > *v) {
continue; continue;
@ -118,103 +123,29 @@ impl Maze {
for (new_dir, new_position, new_cost) in facing for (new_dir, new_position, new_cost) in facing
.reachable() .reachable()
.iter() .iter()
.map(|dir| (dir, (position.0 + dir.ofs().0, position.1 + dir.ofs().1))) .map(|dir| (dir, &position + dir.ofs()))
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#')) .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 })) .map(|(dir, pos)| (dir, pos, if *dir == facing { cost + 1 } else { cost + 1001 }))
{ {
if distances if distances
.get(&(new_position, *new_dir)) .get(&(new_position, *new_dir))
.is_none_or(|best_cost| new_cost < *best_cost) .is_none_or(|best_cost| new_cost <= *best_cost)
{ {
queue.push(State { queue.push(State {
cost: new_cost, cost: new_cost,
position: new_position, position: new_position,
facing: *new_dir, facing: *new_dir,
});
distances.insert((new_position, *new_dir), new_cost);
}
}
}
usize::MAX
}
fn path_dijkstra(&mut self) -> (usize, Vec<Vec<Coord>>) {
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start");
let start = (start_x.try_into().unwrap(), start_y.try_into().unwrap());
let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish");
let finish = (finish_x.try_into().unwrap(), finish_y.try_into().unwrap());
let mut distances = HashMap::new();
let mut queue = BinaryHeap::with_capacity(self.map.data.len());
let mut best_paths = Vec::new();
let mut best_cost = usize::MAX;
distances.insert((start, FacingDirection::East), 0);
queue.push(PathState {
state: State {
cost: 0,
position: start,
facing: FacingDirection::East,
},
path: Vec::with_capacity(100),
});
while let Some(PathState { state, path }) = queue.pop() {
let mut new_path = path.clone();
new_path.push(state.position);
if state.position == finish {
if state.cost < best_cost {
best_paths.clear();
best_paths.push(new_path);
best_cost = state.cost
} else if state.cost == best_cost {
best_paths.push(new_path);
}
continue;
}
if distances
.get(&(state.position, state.facing))
.is_some_and(|v| state.cost > *v)
{
continue;
}
for (new_dir, new_position, new_cost) in state
.facing
.reachable()
.iter()
.map(|dir| (dir, (state.position.0 + dir.ofs().0, state.position.1 + dir.ofs().1)))
.filter(|(_, pos)| self.map.get(pos).is_some_and(|c| *c != b'#'))
.map(|(dir, pos)| {
(
dir,
pos,
if *dir == state.facing {
state.cost + 1
} else {
state.cost + 1001
},
)
})
{
if distances
.get(&(new_position, *new_dir))
.is_none_or(|best_cost| new_cost <= *best_cost)
{
queue.push(PathState {
state: State {
cost: new_cost,
position: new_position,
facing: *new_dir,
},
path: new_path.clone(), path: new_path.clone(),
}); });
distances.insert((new_position, *new_dir), new_cost); distances.insert((new_position, *new_dir), new_cost);
} }
} }
} }
return (best_cost, best_paths); if best_cost == usize::MAX || !RECORD_PATHS {
panic!("no path found");
} else {
return best_cost;
}
} }
} }
@ -224,17 +155,19 @@ fn parse(input: &str) -> Maze {
#[aoc(day16, part1)] #[aoc(day16, part1)]
pub fn part1(input: &str) -> usize { pub fn part1(input: &str) -> usize {
let maze = parse(input); let mut maze = parse(input);
maze.dijkstra() maze.dijkstra::<false>()
} }
#[aoc(day16, part2)] #[aoc(day16, part2)]
pub fn part2(input: &str) -> usize { pub fn part2(input: &str) -> usize {
let mut maze = parse(input); let mut maze = parse(input);
let best_paths = maze.path_dijkstra(); let best_cost = maze.dijkstra::<true>();
let best_paths = maze.paths.get(&best_cost).unwrap();
let best_path_tiles = best_paths.into_iter().flatten().collect_vec();
let mut path_map = maze.map.clone(); let mut path_map = maze.map.clone();
for tile in best_paths.1.into_iter().flatten() { for tile in best_path_tiles {
path_map.set(&tile, b'O'); path_map.set(&tile, b'O');
} }
path_map.count(&b'O') path_map.count(&b'O')

View File

@ -63,22 +63,60 @@ impl AsCoord2d for &Coord2d {
} }
} }
impl<T> AsCoord2d for (T, T) impl AsCoord2d for (i32, i32) {
where
T: Copy + TryInto<i64>,
<T as TryInto<i64>>::Error: Debug,
{
fn to_coord(self) -> Coord2d { fn to_coord(self) -> Coord2d {
Coord2d { Coord2d {
x: self.0.try_into().unwrap(), x: self.0.into(),
y: self.1.try_into().unwrap(), y: self.1.into(),
} }
} }
fn x(&self) -> i64 { fn x(&self) -> i64 {
self.0.try_into().unwrap() self.0.into()
} }
fn y(&self) -> i64 { fn y(&self) -> i64 {
self.1.try_into().unwrap() self.1.into()
}
}
impl AsCoord2d for (i64, i64) {
fn to_coord(self) -> Coord2d {
Coord2d { x: self.0, y: self.1 }
}
fn x(&self) -> i64 {
self.0
}
fn y(&self) -> i64 {
self.1
}
}
impl AsCoord2d for (usize, usize) {
fn to_coord(self) -> Coord2d {
Coord2d {
x: self.0 as i64,
y: self.1 as i64,
}
}
fn x(&self) -> i64 {
self.0 as i64
}
fn y(&self) -> i64 {
self.1 as i64
}
}
impl AsCoord2d for (u64, u64) {
fn to_coord(self) -> Coord2d {
Coord2d {
x: self.0 as i64,
y: self.1 as i64,
}
}
fn x(&self) -> i64 {
self.0 as i64
}
fn y(&self) -> i64 {
self.1 as i64
} }
} }