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) {
|
pub fn walk(&self, l: usize, c: usize) -> (usize, usize) {
|
||||||
match self {
|
match self {
|
||||||
Direction::Up => (l - 1, c),
|
Direction::Up => (l - 1, c),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue