diff --git a/samples/day23.txt b/samples/day23.txt new file mode 100644 index 0000000..65c7c3d --- /dev/null +++ b/samples/day23.txt @@ -0,0 +1,23 @@ +#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.# \ No newline at end of file diff --git a/src/bin/day23.rs b/src/bin/day23.rs new file mode 100644 index 0000000..dd392b5 --- /dev/null +++ b/src/bin/day23.rs @@ -0,0 +1,180 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use aoc_2023::*; +use itertools::iproduct; + +type Output1 = isize; +type Output2 = Output1; + +type Coord = Vector2D; + +lazy_static! { + static ref LEFT: Coord = Coord::new(-1, 0); + static ref RIGHT: Coord = Coord::new(1, 0); + static ref UP: Coord = Coord::new(0, -1); + static ref DOWN: Coord = Coord::new(0, 1); + static ref DIRECTIONS: Vec = vec![*LEFT, *RIGHT, *UP, *DOWN]; + static ref EXITS: HashMap> = HashMap::from([ + ('<', vec![*LEFT]), + ('>', vec![*RIGHT]), + ('^', vec![*UP]), + ('v', vec![*DOWN]), + ('.', DIRECTIONS.clone()), + ('#', vec![]), + ]); +} + +#[derive(Debug, Clone, Copy)] +struct Edge { + start: usize, + end: usize, + distance: usize, +} + +#[derive(Debug, Clone)] +struct Graph { + nodes: Vec, + edges: Vec, +} + +struct Day23 { + graph: Graph, + graph_no_slopes: Graph, +} + +fn char_identity(c: char) -> char { + c +} +fn slope_remover(c: char) -> char { + if ">v<^".contains(c) { + '.' + } else { + c + } +} + +impl Day23 { + fn make_graph(lines: &[String], transform: &dyn Fn(char) -> char) -> Graph { + let map: HashMap = lines + .iter() + .enumerate() + .flat_map(|(y, row)| { + row.chars() + .enumerate() + .map(move |(x, c)| (Coord::new(x as isize, y as isize), transform(c))) + }) + .collect(); + let is_free = |p| map.get(&p).is_some_and(|&c| c != '#'); + let is_road = |p| is_free(p) && DIRECTIONS.iter().filter(|&&d| is_free(p + d)).count() == 2; + let distance = |src, dst| { + let mut q: VecDeque<(Vector2D, usize)> = VecDeque::new(); + q.push_back((src, 0)); + + let mut visited = HashSet::new(); + visited.insert(src); + + while let Some((u, d)) = q.pop_front() { + for direction in &EXITS[&map[&u]] { + let v = u + *direction; + + if v == dst { + return Some(d + 1); + } else if is_road(v) && !visited.contains(&v) { + visited.insert(v); + q.push_back((v, d + 1)); + } + } + } + + None + }; + + let nodes_coords = map + .keys() + .cloned() + .filter(|p| is_free(*p) && !is_road(*p)) + .sorted_by_key(|p| (p.y(), p.x())) + .collect_vec(); + let nodes = (0..nodes_coords.len()).map(|i| 1_usize << i).collect_vec(); + + let edges = iproduct!(0..nodes_coords.len(), 0..nodes_coords.len()) + .filter(|(i, j)| i != j) + .filter_map(|(i, j)| { + distance(nodes_coords[i], nodes_coords[j]).map(|d| Edge { + start: nodes[i], + end: nodes[j], + distance: d, + }) + }) + .collect_vec(); + + Graph { nodes, edges } + } + + fn solve(g: &Graph) -> isize { + let start = *g.nodes.first().unwrap(); + let goal = *g.nodes.last().unwrap(); + + fn longest_path( + cache: &mut HashMap<(usize, usize), isize>, + edges: &[Edge], + goal: usize, + u: usize, + visited: usize, + ) -> isize { + if u == goal { + return 0; + } else if (visited & u) != 0 { + return i32::MIN as isize; + } + + let key = (u, visited); + let missing = !cache.contains_key(&key); + let distance = missing.then(|| { + edges + .iter() + .filter_map(|e| { + (e.start == u).then(|| { + e.distance as isize + + longest_path(cache, edges, goal, e.end, visited | u) + }) + }) + .max() + .unwrap() + }); + + *cache.entry(key).or_insert_with(|| distance.unwrap()) + } + + let mut cache: HashMap<(usize, usize), isize> = HashMap::new(); + longest_path(&mut cache, &g.edges, goal, start, 0) + } +} + +impl Solution for Day23 { + fn new>(pathname: P) -> Self { + let lines: Vec = file_to_lines(pathname); + + let graph = Self::make_graph(&lines, &char_identity); + let graph_no_slopes = Self::make_graph(&lines, &slope_remover); + + Self { + graph, + graph_no_slopes, + } + } + + fn part_1(&mut self) -> Output1 { + Self::solve(&self.graph) + } + + fn part_2(&mut self) -> Output2 { + Self::solve(&self.graph_no_slopes) + } +} + +fn main() -> Result<()> { + Day23::main() +} + +test_sample!(day_23, Day23, 94, 154);