Compare commits

..

2 Commits

Author SHA1 Message Date
20e6889572
day16: improve perf again by going to i16 positions
All checks were successful
test / AoC 2024 (push) Successful in 5m29s
map dimensions fit in i16, so use those in data structures

make grid support generic position type as long as they implement to i64
2024-12-16 01:06:39 -08:00
755fbbc53d
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
2024-12-16 00:58:15 -08:00
2 changed files with 127 additions and 98 deletions

View File

@ -1,13 +1,14 @@
use aoc_runner_derive::aoc;
use grid::{AsCoord2d, Coord2d, Grid};
use itertools::Itertools;
use grid::Grid;
use std::{
collections::{BinaryHeap, HashMap},
i64,
str::FromStr,
usize,
};
type CoordType = i16;
type Coord = (CoordType, CoordType);
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
enum FacingDirection {
East,
@ -17,7 +18,7 @@ enum FacingDirection {
}
impl FacingDirection {
fn ofs(&self) -> (i64, i64) {
fn ofs(&self) -> (CoordType, CoordType) {
match self {
FacingDirection::East => (1, 0),
FacingDirection::South => (0, 1),
@ -37,9 +38,8 @@ impl FacingDirection {
#[derive(Clone, Eq, PartialEq, Debug)]
struct State {
cost: usize,
position: Coord2d,
position: Coord,
facing: FacingDirection,
path: Vec<Coord2d>,
}
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 {
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 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 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;
}
return cost;
}
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
.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<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(),
});
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 +224,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')

View File

@ -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 {
Coord2d {
x: self.0.into(),
y: self.1.into(),
x: self.0.try_into().unwrap(),
y: self.1.try_into().unwrap(),
}
}
fn x(&self) -> i64 {
self.0.into()
self.0.try_into().unwrap()
}
fn y(&self) -> i64 {
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
self.1.try_into().unwrap()
}
}