day 16
This commit is contained in:
parent
8ae1057768
commit
8103dc20a5
2 changed files with 275 additions and 0 deletions
266
src/bin/day16.rs
Normal file
266
src/bin/day16.rs
Normal 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(())
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue