Compare commits
2 Commits
4b85a90635
...
20e6889572
Author | SHA1 | Date | |
---|---|---|---|
20e6889572 | |||
755fbbc53d |
169
src/day16.rs
169
src/day16.rs
@ -1,13 +1,14 @@
|
|||||||
use aoc_runner_derive::aoc;
|
use aoc_runner_derive::aoc;
|
||||||
use grid::{AsCoord2d, Coord2d, Grid};
|
use grid::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,
|
||||||
@ -17,7 +18,7 @@ enum FacingDirection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FacingDirection {
|
impl FacingDirection {
|
||||||
fn ofs(&self) -> (i64, i64) {
|
fn ofs(&self) -> (CoordType, CoordType) {
|
||||||
match self {
|
match self {
|
||||||
FacingDirection::East => (1, 0),
|
FacingDirection::East => (1, 0),
|
||||||
FacingDirection::South => (0, 1),
|
FacingDirection::South => (0, 1),
|
||||||
@ -37,9 +38,8 @@ impl FacingDirection {
|
|||||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
cost: usize,
|
cost: usize,
|
||||||
position: Coord2d,
|
position: Coord,
|
||||||
facing: FacingDirection,
|
facing: FacingDirection,
|
||||||
path: Vec<Coord2d>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for State {
|
impl Ord for State {
|
||||||
@ -58,62 +58,57 @@ 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, paths })
|
Ok(Self { map })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Maze {
|
impl Maze {
|
||||||
fn dijkstra<const RECORD_PATHS: bool>(&mut self) -> usize {
|
fn dijkstra(&self) -> usize {
|
||||||
let start = self.map.find(&b'S').expect("can't find start").to_coord();
|
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start");
|
||||||
let finish = self.map.find(&b'E').expect("can't find finish").to_coord();
|
let start = (start_x as CoordType, start_y as CoordType);
|
||||||
|
|
||||||
|
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::with_capacity(self.map.data.len());
|
let mut queue = BinaryHeap::new();
|
||||||
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 {
|
while let Some(State { cost, position, facing }) = queue.pop() {
|
||||||
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 {
|
return cost;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) {
|
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) {
|
||||||
@ -123,29 +118,103 @@ 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 + dir.ofs()))
|
.map(|dir| (dir, (position.0 + dir.ofs().0, position.1 + dir.ofs().1)))
|
||||||
.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if best_cost == usize::MAX || !RECORD_PATHS {
|
return (best_cost, best_paths);
|
||||||
panic!("no path found");
|
|
||||||
} else {
|
|
||||||
return best_cost;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,19 +224,17 @@ fn parse(input: &str) -> Maze {
|
|||||||
|
|
||||||
#[aoc(day16, part1)]
|
#[aoc(day16, part1)]
|
||||||
pub fn part1(input: &str) -> usize {
|
pub fn part1(input: &str) -> usize {
|
||||||
let mut maze = parse(input);
|
let maze = parse(input);
|
||||||
maze.dijkstra::<false>()
|
maze.dijkstra()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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_cost = maze.dijkstra::<true>();
|
let best_paths = maze.path_dijkstra();
|
||||||
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_path_tiles {
|
for tile in best_paths.1.into_iter().flatten() {
|
||||||
path_map.set(&tile, b'O');
|
path_map.set(&tile, b'O');
|
||||||
}
|
}
|
||||||
path_map.count(&b'O')
|
path_map.count(&b'O')
|
||||||
|
@ -63,60 +63,22 @@ impl AsCoord2d for &Coord2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsCoord2d for (i32, i32) {
|
impl<T> AsCoord2d for (T, T)
|
||||||
|
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.into(),
|
x: self.0.try_into().unwrap(),
|
||||||
y: self.1.into(),
|
y: self.1.try_into().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn x(&self) -> i64 {
|
fn x(&self) -> i64 {
|
||||||
self.0.into()
|
self.0.try_into().unwrap()
|
||||||
}
|
}
|
||||||
fn y(&self) -> i64 {
|
fn y(&self) -> i64 {
|
||||||
self.1.into()
|
self.1.try_into().unwrap()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user