This commit is contained in:
Quentin Boyer 2024-12-16 20:53:40 +01:00
parent 8ae1057768
commit 8103dc20a5
2 changed files with 275 additions and 0 deletions

266
src/bin/day16.rs Normal file
View file

@ -0,0 +1,266 @@
use std::{
collections::{BinaryHeap, HashMap, HashSet},
hash::Hash,
time::Instant,
};
use aoc_2024::{load, print_res, Direction};
use arrayvec::ArrayVec;
use bstr::{BString, ByteSlice};
use color_eyre::eyre::{bail, ContextCompat};
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct Point {
line: usize,
col: usize,
}
type Parsed = (Point, Point, Vec<Vec<bool>>);
#[inline(never)]
pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
let mut start = None;
let mut end = None;
let mut grid = Vec::new();
for (l, line) in input.lines().enumerate() {
let mut row = Vec::new();
for (c, &t) in line.iter().enumerate() {
match t {
b'#' => row.push(false),
b'.' => row.push(true),
b'S' => {
start = Some(Point { line: l, col: c });
row.push(true);
}
b'E' => {
end = Some(Point { line: l, col: c });
row.push(true);
}
_ => bail!("Unknown char: `{}`", t as char),
}
}
grid.push(row);
}
Ok((
start.with_context(|| "Could not find start")?,
end.with_context(|| "Could not find end")?,
grid,
))
}
#[derive(Debug)]
struct PathInfo {
cost: u64,
from: ArrayVec<(Direction, Point), 4>,
}
struct BestPaths {
paths: HashMap<(Direction, Point), PathInfo>,
}
fn best_paths((start, end, grid): Parsed) -> BestPaths {
#[derive(Debug)]
struct Position {
cost: u64,
direction: Direction,
current: Point,
}
impl PartialEq for Position {
fn eq(&self, other: &Self) -> bool {
self.cost == other.cost
}
}
impl Eq for Position {}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Position {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.cost.cmp(&self.cost)
}
}
fn try_visit(
from: (Direction, Point),
dest: Position,
seen: &mut HashMap<(Direction, Point), PathInfo>,
paths: &mut BinaryHeap<Position>,
) {
match seen.get_mut(&(dest.direction, dest.current)) {
Some(info) if info.cost < dest.cost => (),
Some(info) if info.cost == dest.cost => {
if !info.from.contains(&from) {
info.from.push(from);
}
}
_ => {
let mut f = ArrayVec::new();
f.push(from);
seen.insert(
(dest.direction, dest.current),
PathInfo {
cost: dest.cost,
from: f,
},
);
paths.push(dest);
}
}
}
let mut seen = HashMap::new();
seen.insert(
(Direction::Left, start),
PathInfo {
cost: 0,
from: ArrayVec::new(),
},
);
let mut paths = BinaryHeap::new();
paths.push(Position {
cost: 0,
direction: Direction::Left,
current: start,
});
let mut end_cost = None;
loop {
let best = paths.pop().unwrap();
if let Some(cost) = end_cost {
if best.cost > cost {
return BestPaths { paths: seen };
}
}
if best.current == end {
match end_cost {
Some(cost) if cost < best.cost => continue,
_ => end_cost = Some(best.cost),
}
}
match seen.get(&(best.direction, best.current)) {
Some(info) if info.cost < best.cost => continue,
_ => (),
}
let (line, col) = best.direction.walk(best.current.line, best.current.col);
if grid[line][col] {
try_visit(
(best.direction, best.current),
Position {
cost: best.cost + 1,
direction: best.direction,
current: Point { line, col },
},
&mut seen,
&mut paths,
);
}
try_visit(
(best.direction, best.current),
Position {
cost: best.cost + 1000,
direction: best.direction.turn_right(),
current: best.current,
},
&mut seen,
&mut paths,
);
try_visit(
(best.direction, best.current),
Position {
cost: best.cost + 1000,
direction: best.direction.turn_left(),
current: best.current,
},
&mut seen,
&mut paths,
);
}
}
impl BestPaths {
fn best_cost(&self, at: Point) -> Option<&PathInfo> {
Direction::all()
.iter()
.filter_map(|&d| self.paths.get(&(d, at)))
.min_by_key(|i| i.cost)
}
}
#[inline(never)]
pub fn part1(input: Parsed) {
let end = input.1;
let best_paths = best_paths(input);
print_res!("Fastest path: {}", best_paths.best_cost(end).unwrap().cost);
}
impl BestPaths {
fn retrace(&self, from: Point) -> HashSet<Point> {
let from_info = self.best_cost(from).unwrap();
let mut seen = HashSet::new();
seen.insert(from);
let mut paths = from_info.from.to_vec();
while !paths.is_empty() {
let mut new_paths = Vec::new();
for path in paths {
seen.insert(path.1);
let info = self.paths.get(&path).unwrap();
new_paths.extend(info.from.clone());
}
paths = new_paths;
}
seen
}
}
#[inline(never)]
pub fn part2(input: Parsed) {
let end = input.1;
let best_paths = best_paths(input);
print_res!(
"Tile count in best paths: {}",
best_paths.retrace(end).len()
);
}
pub fn main() -> color_eyre::Result<()> {
let context = load()?;
let start = Instant::now();
let parsed = parsing(&context.input)?;
let elapsed = humantime::format_duration(start.elapsed());
let start = Instant::now();
if context.part == 1 {
part1(parsed);
} else {
part2(parsed);
}
let elapsed_part = humantime::format_duration(start.elapsed());
println!(" Parsing: {elapsed}");
println!(" Solving: {elapsed_part}");
Ok(())
}

View file

@ -47,6 +47,15 @@ impl Direction {
}
}
pub fn turn_left(self) -> Self {
match self {
Self::Up => Self::Left,
Self::Left => Self::Down,
Self::Down => Self::Right,
Self::Right => Self::Up,
}
}
pub fn walk(&self, l: usize, c: usize) -> (usize, usize) {
match self {
Direction::Up => (l - 1, c),