From cd4268a9cedf7e858a43b7f1638b503485c67878 Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Sat, 4 Jan 2025 20:07:25 +0100 Subject: [PATCH] Day 18 --- src/bin/day18.rs | 211 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/bin/day18.rs diff --git a/src/bin/day18.rs b/src/bin/day18.rs new file mode 100644 index 0000000..76c730e --- /dev/null +++ b/src/bin/day18.rs @@ -0,0 +1,211 @@ +use std::{ + collections::{BinaryHeap, HashMap, HashSet}, + time::Instant, +}; + +use aoc_2024::{load, print_res}; +use bstr::BString; +use color_eyre::eyre::OptionExt; + +type Parsed = Vec<(usize, usize)>; + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + std::str::from_utf8(input)? + .lines() + .map(|l| { + let (x, y) = l.split_once(',').ok_or_eyre("Missing ,")?; + + Ok((x.parse()?, y.parse()?)) + }) + .collect() +} + +fn a_star(grid: &[[bool; GRID_SIZE]; GRID_SIZE]) -> Option { + #[derive(Clone, PartialEq, Eq)] + struct Path { + x: usize, + y: usize, + score: usize, + } + + impl Path { + fn heuristic(&self) -> usize { + self.score + (GRID_SIZE - 1 - self.x) + (GRID_SIZE - 1 - self.y) + } + } + + impl Ord for Path { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other.heuristic().cmp(&self.heuristic()) + } + } + + impl PartialOrd for Path { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + let mut prev = HashMap::new(); + + let mut visited = HashMap::new(); + visited.insert((0, 0), 0); + + let mut current = BinaryHeap::new(); + current.push(Path { + x: 0, + y: 0, + score: 0, + }); + + while let Some(p) = current.pop() { + if p.x == GRID_SIZE - 1 && p.y == GRID_SIZE - 1 { + return Some(p.score); + } + + let mut try_insert = |n: Path| { + if !grid[n.x][n.y] { + match visited.get(&(n.x, n.y)) { + Some(&s) if s >= n.score => (), + _ => { + visited.insert((n.x, n.y), n.score); + prev.insert((n.x, n.y), (p.x, p.y)); + current.push(n); + } + } + } + }; + + let x = p.x; + let y = p.y; + let score = p.score + 1; + + if x > 0 { + let new_path = Path { x: x - 1, y, score }; + try_insert(new_path); + } + + if x < GRID_SIZE - 1 { + let new_path = Path { x: x + 1, y, score }; + try_insert(new_path); + } + + if y > 0 { + let new_path = Path { x, y: y - 1, score }; + try_insert(new_path); + } + + if y < GRID_SIZE - 1 { + let new_path = Path { x, y: y + 1, score }; + try_insert(new_path); + } + } + + None +} + +const GRID_SIZE: usize = 70 + 1; +const CUTTOF: usize = 1024; + +#[inline(never)] +pub fn part1(input: Parsed) { + let mut grid = [[false; GRID_SIZE]; GRID_SIZE]; + + for &(x, y) in &input[0..CUTTOF] { + grid[x][y] = true; + } + + let result = a_star(&grid).unwrap(); + print_res!("Steps to take: {result}"); +} + +fn bfs(grid: &[[bool; GRID_SIZE]; GRID_SIZE]) -> Option> { + let mut seen = HashMap::new(); + seen.insert((0, 0), (0, 0)); + + let mut paths = Vec::new(); + paths.push((0, 0)); + + while let Some((x, y)) = paths.pop() { + if x == GRID_SIZE - 1 && y == GRID_SIZE - 1 { + let mut paths = HashSet::new(); + let mut current = (x, y); + + while current != (0, 0) { + paths.insert(current); + current = seen[¤t]; + } + + return Some(paths); + } + + let mut try_insert = |nx: usize, ny: usize| { + if !grid[nx][ny] && !seen.contains_key(&(nx, ny)) { + seen.insert((nx, ny), (x, y)); + paths.push((nx, ny)); + } + }; + + if x > 0 { + try_insert(x - 1, y); + } + + if x < GRID_SIZE - 1 { + try_insert(x + 1, y); + } + + if y > 0 { + try_insert(x, y - 1); + } + + if y < GRID_SIZE - 1 { + try_insert(x, y + 1); + } + } + + None +} + +#[inline(never)] +pub fn part2(input: Parsed) { + let mut grid = [[false; GRID_SIZE]; GRID_SIZE]; + let mut seen = bfs(&grid).unwrap(); + + for (x, y) in input { + grid[x][y] = true; + + if seen.contains(&(x, y)) { + match bfs(&grid) { + Some(p) => seen = p, + None => { + print_res!("Breaking block: {x},{y}"); + return; + } + } + } + } + + panic!("Did not find a breaking block"); +} + +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(()) +}