diff --git a/day18/Cargo.toml b/day18/Cargo.toml new file mode 100644 index 0000000..ebcf320 --- /dev/null +++ b/day18/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day18" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day18/src/main.rs b/day18/src/main.rs new file mode 100644 index 0000000..6dc9ce2 --- /dev/null +++ b/day18/src/main.rs @@ -0,0 +1,181 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::BinaryHeap; + +use std::env; +use std::fs; + +use crate::Dir::{North, South, East, West }; + +type Coord = (i32, i32); +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] +enum Dir { North, South, East, West } + +fn step(start: &Coord, d: Dir, steps: i32) -> Coord { + match d { + North => (start.0, start.1 - steps), + South => (start.0, start.1 + steps), + East => (start.0 + steps, start.1), + West => (start.0 - steps, start.1), + } +} + +fn main() { + println!("Hello, AoC day 13!"); + + let args: Vec = env::args().collect(); + if args.len() != 2 { + println!("wrong number of arguments!"); + std::process::exit(1); + } + + let file_path = &args[1]; + + let file = fs::read_to_string(file_path) + .expect("should be able to read the file"); + + let positions: Vec = file.lines() + .filter(|l| l.len() != 0) + .map(|l| l.split_once(",").unwrap()) + .map(|(left, right)| (left.parse().unwrap(), right.parse().unwrap())) + .collect(); + + println!("read in {} positions", positions.len()); + + + let mut corner = (0,0); + positions.iter().for_each(|pos| { + corner.0 = corner.0.max(pos.0); + corner.1 = corner.1.max(pos.1); + }); + + let mut map = HashMap::new(); + + for y in 0..corner.1+1 { + for x in 0..corner.0+1 { + map.insert((x,y), '.'); + } + } + + let count = 1024; + for &(x, y) in &positions[..count] { + map.insert((x,y), '#'); + } + + print_map(&map); + + + let score = solve_maze(&map, (0,0), corner); + + println!("the shortest path after {count} steps was {score:?}"); + + println!("let's do a binary blame search!"); + + let (mut lower, mut upper): (usize, usize) = (0,positions.len()); + while lower < upper - 1 { + println!("checking if it's in [{},{})", lower, upper); + let middle = lower + ((upper - lower) / 2); + let test_map = map_at_time(&positions[..middle], corner); + //print_map(&test_map); + if let Some(_) = solve_maze(&test_map, (0,0), corner) { + // if there was a solution, up the lower bound + lower = middle; + } else { + // if no solution, lower the upper bound + upper = middle; + } + } + + println!("it's no longer solved at {}ns when byte {},{} falls", lower, positions[lower].0, positions[lower].1); +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct State { + cost: u32, + heuristic: u32, + position: Coord, +} + +impl Ord for State { + fn cmp(&self, other: &Self) -> Ordering { + // flipped ordering on cost, so that it's a min-heap and not a max-heap + (other.cost + other.heuristic).cmp(&(self.cost + self.heuristic)) + .then_with(|| self.position.cmp(&other.position)) + } +} + +// `PartialOrd` needs to be implemented as well. +impl PartialOrd for State { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn solve_maze(map: &HashMap, start: Coord, exit: Coord) -> Option { + let mut heap = BinaryHeap::new(); + let mut minimals: HashMap = HashMap::new(); + + heap.push(State { position: start, cost: 0, heuristic: hfunc(start, exit) }); + while let Some(state) = heap.pop() { + let State { position, cost, heuristic: _ } = state; + if position == exit { return Some(cost); } + + if let Some(old_state) = minimals.get(&position) { + if *old_state >= (State { position, cost, heuristic: 0 }) { continue; } + } + + minimals.insert(position, State { position, cost, heuristic: 0 }); + + for new_dir in [North, South, East, West] { + let new_spot = step(&position, new_dir, 1); + + if map.get(&new_spot) == Some(&'.') { + heap.push(State { + position: new_spot, + cost: cost + 1, + heuristic: hfunc(new_spot, exit), + }); + } + } + } + + return None; +} + +fn hfunc(pos: Coord, end: Coord) -> u32 { + return ((pos.0-end.0).abs() + (pos.1-end.1).abs()).try_into().unwrap(); +} + +fn print_map(map: &HashMap) { + let mut corner = (0,0); + for (p, _) in map { + corner.0 = corner.0.max(p.0); + corner.1 = corner.1.max(p.1); + } + + for y in 0..corner.1+1 { + for x in 0..corner.0+1 { + print!("{}", map.get(&(x,y)).unwrap_or(&' ')); + } + print!("\n"); + } + + print!("\n"); +} + +fn map_at_time(bytes: &[Coord], corner: Coord) -> HashMap{ + let mut map = HashMap::with_capacity((corner.0*corner.1).try_into().unwrap()); + + for y in 0..corner.1+1 { + for x in 0..corner.0+1 { + map.insert((x,y), '.'); + } + } + + for &(x, y) in bytes { + map.insert((x,y), '#'); + } + + return map; +}