day16: refactor, optimize
split path recording and best cost functions for big gainz use i32 instead of i64 positions to shrink data structures for some gainz
This commit is contained in:
parent
4b85a90635
commit
755fbbc53d
162
src/day16.rs
162
src/day16.rs
@ -1,9 +1,7 @@
|
||||
use aoc_runner_derive::aoc;
|
||||
use grid::{AsCoord2d, Coord2d, Grid};
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
collections::{BinaryHeap, HashMap},
|
||||
i64,
|
||||
str::FromStr,
|
||||
usize,
|
||||
};
|
||||
@ -17,7 +15,7 @@ enum FacingDirection {
|
||||
}
|
||||
|
||||
impl FacingDirection {
|
||||
fn ofs(&self) -> (i64, i64) {
|
||||
fn ofs(&self) -> (i32, i32) {
|
||||
match self {
|
||||
FacingDirection::East => (1, 0),
|
||||
FacingDirection::South => (0, 1),
|
||||
@ -37,9 +35,8 @@ impl FacingDirection {
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
struct State {
|
||||
cost: usize,
|
||||
position: Coord2d,
|
||||
position: (i32, i32),
|
||||
facing: FacingDirection,
|
||||
path: Vec<Coord2d>,
|
||||
}
|
||||
|
||||
impl Ord for State {
|
||||
@ -58,63 +55,58 @@ impl PartialOrd for State {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
struct PathState {
|
||||
state: State,
|
||||
path: Vec<(i32, i32)>,
|
||||
}
|
||||
|
||||
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 {
|
||||
map: Grid<u8>,
|
||||
paths: HashMap<usize, Vec<Vec<Coord2d>>>,
|
||||
}
|
||||
|
||||
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()?;
|
||||
let paths = HashMap::new();
|
||||
|
||||
Ok(Self { map, paths })
|
||||
Ok(Self { map })
|
||||
}
|
||||
}
|
||||
|
||||
impl Maze {
|
||||
fn dijkstra<const RECORD_PATHS: bool>(&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();
|
||||
fn dijkstra(&self) -> usize {
|
||||
let (start_x, start_y) = self.map.find(&b'S').expect("can't find start");
|
||||
let start = (start_x as i32, start_y as i32);
|
||||
|
||||
let (finish_x, finish_y) = self.map.find(&b'E').expect("can't find finish");
|
||||
let finish = (finish_x as i32, finish_y as i32);
|
||||
|
||||
let mut distances = HashMap::new();
|
||||
let mut queue = BinaryHeap::with_capacity(self.map.data.len());
|
||||
let mut best_cost = usize::MAX;
|
||||
let mut queue = BinaryHeap::new();
|
||||
|
||||
distances.insert((start, FacingDirection::East), 0);
|
||||
queue.push(State {
|
||||
cost: 0,
|
||||
position: start,
|
||||
facing: FacingDirection::East,
|
||||
path: Vec::new(),
|
||||
});
|
||||
|
||||
while let Some(State {
|
||||
cost,
|
||||
position,
|
||||
facing,
|
||||
path,
|
||||
}) = queue.pop()
|
||||
{
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(position);
|
||||
|
||||
while let Some(State { cost, position, facing }) = queue.pop() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if distances.get(&(position, facing)).is_some_and(|v| cost > *v) {
|
||||
continue;
|
||||
@ -123,29 +115,103 @@ impl Maze {
|
||||
for (new_dir, new_position, new_cost) in facing
|
||||
.reachable()
|
||||
.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'#'))
|
||||
.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)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
usize::MAX
|
||||
}
|
||||
fn path_dijkstra(&mut self) -> (usize, Vec<Vec<(i32, i32)>>) {
|
||||
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(),
|
||||
});
|
||||
distances.insert((new_position, *new_dir), new_cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
if best_cost == usize::MAX || !RECORD_PATHS {
|
||||
panic!("no path found");
|
||||
} else {
|
||||
return best_cost;
|
||||
}
|
||||
return (best_cost, best_paths);
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,19 +221,17 @@ fn parse(input: &str) -> Maze {
|
||||
|
||||
#[aoc(day16, part1)]
|
||||
pub fn part1(input: &str) -> usize {
|
||||
let mut maze = parse(input);
|
||||
maze.dijkstra::<false>()
|
||||
let maze = parse(input);
|
||||
maze.dijkstra()
|
||||
}
|
||||
|
||||
#[aoc(day16, part2)]
|
||||
pub fn part2(input: &str) -> usize {
|
||||
let mut maze = parse(input);
|
||||
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 best_paths = maze.path_dijkstra();
|
||||
|
||||
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.count(&b'O')
|
||||
|
Loading…
x
Reference in New Issue
Block a user