diff --git a/Cargo.toml b/Cargo.toml index 91ba807..cb3a1f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "template", "xtask", "day1" -, "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12", "day13", "day14", "day15", "day16", "day17", "day18", "day19", "day20", "day21", "day22"] +, "day2", "day3", "day4", "day5", "day6", "day7", "day8", "day9", "day10", "day11", "day12", "day13", "day14", "day15", "day16", "day17", "day18", "day19", "day20", "day21", "day22", "day23"] [workspace.dependencies] # Flexible concrete Error type built on std::error::Error diff --git a/day23/Cargo.toml b/day23/Cargo.toml new file mode 100644 index 0000000..3f024cf --- /dev/null +++ b/day23/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "day23" +version = "0.1.0" +edition = "2021" + +[dependencies] +tracing = {workspace = true} +tracing-test = {workspace = true} +anyhow = {workspace = true} +regex = {workspace = true} +lazy_static = {workspace = true} +pathfinding = {workspace = true} +itertools = {workspace = true} + +[dependencies.utils] +path = "../utils" \ No newline at end of file diff --git a/day23/src/lib.rs b/day23/src/lib.rs new file mode 100644 index 0000000..e0e72d7 --- /dev/null +++ b/day23/src/lib.rs @@ -0,0 +1,194 @@ +use std::{ + collections::{HashMap, HashSet}, + io::{BufRead, BufReader}, +}; +use tracing::debug; +use utils::Matrix; + +pub type ResultType = u64; + +#[derive(Debug, Default)] +pub struct Solution { + tiles: Matrix, +} +impl Solution { + fn set_tile(&mut self, x: usize, y: usize, c: char) { + self.tiles.set(x as isize, y as isize, c); + } +} + +impl TryFrom> for Solution { + type Error = std::io::Error; + + fn try_from(reader: BufReader) -> Result { + let mut solution = Self::default(); + for (y, line) in reader.lines().map_while(Result::ok).enumerate() { + // Implement for problem + for (x, c) in line.chars().enumerate() { + solution.set_tile(x, y, c); + } + } + Ok(solution) + } +} +impl Solution { + fn longest_path( + &self, + sx: isize, + sy: isize, + ex: isize, + ey: isize, + ignore_slopes: bool, + ) -> ResultType { + let mut max = 0; + let mut remaining = Vec::new(); + remaining.push((sx, sy, 0, HashSet::new())); + while let Some((x, y, d, visited)) = remaining.pop() { + if x == ex && y == ey { + max = std::cmp::max(max, d); + debug!(max, d, "end"); + continue; + } + // Which directions can walk? + let directions = if ignore_slopes { + vec![(0, 1), (0, -1), (1, 0), (-1, 0)] + } else { + match self.tiles.get(x, y).unwrap_or(&'#') { + '.' => vec![(0, 1), (0, -1), (1, 0), (-1, 0)], + '>' => vec![(1, 0)], + '<' => vec![(-1, 0)], + '^' => vec![(0, -1)], + 'v' => vec![(0, 1)], + '#' => panic!("standing in a tree"), + c => panic!("unexpected {c}"), + } + }; + for (dx, dy) in directions { + if !visited.contains(&(x + dx, y + dy)) { + match self.tiles.get(x + dx, y + dy).unwrap_or(&'#') { + '#' => {} + '.' | '>' | 'v' | '<' | '^' => { + let mut n_visited = visited.clone(); + n_visited.insert((x + dx, y + dy)); + remaining.push((x + dx, y + dy, d + 1, n_visited)); + } + c => panic!("unexpected {c}"), + } + } + } + } + max + } + + fn longest_path_part2(&self, sx: isize, sy: isize, ex: isize, ey: isize) -> ResultType { + let mut adjacency = HashMap::new(); + let (max_x, max_y) = self.tiles.dimensions(); + for y in 0..=max_y { + for x in 0..=max_x { + if !matches!(self.tiles.get(x, y).unwrap_or(&'#'), '#') { + let r = adjacency.entry((x, y)).or_insert_with(HashMap::new); + for (dx, dy) in [(0, 1), (0, -1), (1, 0), (-1, 0)] { + if !matches!(self.tiles.get(x + dx, y + dy).unwrap_or(&'#'), '#') { + r.insert((x + dx, y + dy), 1); + } + } + } + } + } + let mut num_compact = 0; + let keys = adjacency.keys().cloned().collect::>(); + for node in keys { + let neighbours: HashMap<(isize, isize), i32> = adjacency.get(&node).unwrap().clone(); + if neighbours.len() != 2 { + continue; + } + num_compact += 1; + // remove 'node' from both neighbours + for (neigh, d) in neighbours.iter() { + let other = neighbours + .iter() + .find(|(other, _)| other.0 != neigh.0 || other.1 != neigh.1) + .unwrap(); + let n: &mut HashMap<(isize, isize), i32> = + adjacency.get_mut(&(neigh.0, neigh.1)).unwrap(); + n.remove(&node); + n.insert(*other.0, other.1 + d); + } + adjacency.remove(&node); + } + debug!(adjacency = debug(&adjacency), num_compact, "adj"); + + let mut max = 0; + let mut remaining = Vec::new(); + remaining.push(((sx, sy), 0, HashSet::new())); + while let Some(((x, y), d, visited)) = remaining.pop() { + if x == ex && y == ey { + if max < d { + debug!(max, d, "end"); + max = d; + } + continue; + } + // Which directions can walk? + let neighbours = adjacency.get(&(x, y)).unwrap(); + for (neighbour, len) in neighbours { + if !visited.contains(&neighbour) { + let mut n_visited = visited.clone(); + n_visited.insert(neighbour); + remaining.push((*neighbour, d + len, n_visited)); + } + } + } + max as ResultType + } +} +impl utils::Solution for Solution { + type Result = anyhow::Result; + fn analyse(&mut self, _is_full: bool) {} + + fn answer_part1(&self, _is_full: bool) -> Self::Result { + // Find start & end + let (max_x, max_y) = self.tiles.dimensions(); + let start = (0..=max_x) + .map(|x| (x, self.tiles.get(x, 0).unwrap_or(&'#'))) + .find(|(_, c)| *c == &'.') + .unwrap() + .0; + let end = (0..=max_x) + .map(|x| (x, self.tiles.get(x, max_y).unwrap_or(&'#'))) + .find(|(_, c)| *c == &'.') + .unwrap() + .0; + + debug!(start, end, "s"); + + let r = self.longest_path(start, 0, end, max_y, false); + debug!(r, "done?"); + // Implement for problem + Ok(r) + } + + fn answer_part2(&self, _is_full: bool) -> Self::Result { + // Find start & end + let (max_x, max_y) = self.tiles.dimensions(); + let start = (0..=max_x) + .map(|x| (x, self.tiles.get(x, 0).unwrap_or(&'#'))) + .find(|(_, c)| *c == &'.') + .unwrap() + .0; + let end = (0..=max_x) + .map(|x| (x, self.tiles.get(x, max_y).unwrap_or(&'#'))) + .find(|(_, c)| *c == &'.') + .unwrap() + .0; + + debug!(start, end, "s"); + + let r = self.longest_path_part2(start, 0, end, max_y); + debug!(r, "done?"); + // Implement for problem + // TOO Low: 6282 + // Did not end, but answer = 6574 + Ok(r) + } +} diff --git a/day23/src/main.rs b/day23/src/main.rs new file mode 100644 index 0000000..da1a3c9 --- /dev/null +++ b/day23/src/main.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use day23::{ResultType, Solution}; + +fn main() -> Result<()> { + utils::log_init(); + + utils::run::(&["sample"], &["full"]) +}