diff --git a/README.md b/README.md index fdbb6c4..436f807 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | [Day 20](./src/bin/20.rs) | `4.3ms` | `18.2ms` | | [Day 21](./src/bin/21.rs) | `1.5ms` | `70.1ms` | | [Day 22](./src/bin/22.rs) | `9.5ms` | `17.6ms` | +| [Day 23](./src/bin/23.rs) | `2.6ms` | `3.1s` | -**Total: 46608.91ms** +**Total: 49711.51ms** --- diff --git a/src/bin/23.rs b/src/bin/23.rs new file mode 100644 index 0000000..272f0d6 --- /dev/null +++ b/src/bin/23.rs @@ -0,0 +1,157 @@ +use std::cmp::max; +use std::collections::{HashSet, HashMap}; + +advent_of_code::solution!(23); + +#[derive(PartialEq)] +enum Tile { + Path, + Forest, + SlopeUp, + SlopeLeft, + SlopeDown, + SlopeRight +} + +type Map = Vec>; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct Coord(isize, isize); + +type Graph = HashMap>; + +impl Coord { + fn neighbours(&self, map: &Map) -> Vec { + let Coord(x, y) = *self; + + let ns: Vec<(isize, isize)> = match map[y as usize][x as usize] { + Tile::Path => vec![(x, y - 1), (x - 1, y), (x, y + 1), (x + 1, y)], + Tile::Forest => vec![], + Tile::SlopeUp => vec![(x, y - 1)], + Tile::SlopeLeft => vec![(x - 1, y)], + Tile::SlopeDown => vec![(x, y + 1)], + Tile::SlopeRight => vec![(x + 1, y)], + }; + + ns.into_iter() + .filter(|&(x,y)| x >= 0 && y >= 0 && x < map[0].len() as isize && y < map.len() as isize) + .filter(|&(x,y)| map[y as usize][x as usize] != Tile::Forest) + .map(|(x, y)| Coord(x, y)) + .collect() + } +} + +fn parse(input: &str) -> Map { + input.lines() + .map(|l| l.chars().map(|c| match c { + '.' => Tile::Path, + '#' => Tile::Forest, + '^' => Tile::SlopeUp, + '<' => Tile::SlopeLeft, + 'v' => Tile::SlopeDown, + '>' => Tile::SlopeRight, + _ => panic!("Invalid char") + }).collect()) + .collect() +} + +fn build_graph(start: Coord, end: Coord, map: &Map) -> Graph { + let mut graph: Graph = HashMap::new(); + + let crossroads: Vec = (0..map.len()).flat_map(|r| + (0..map[0].len()).map(move |c| Coord(c as isize, r as isize))) + .filter(|c| c.neighbours(map).len() >= 3) + .chain([start, end]) + .collect(); + + for c in crossroads.iter().cloned() { + graph.insert(c, HashMap::new()); + } + + for c in crossroads.iter().cloned() { + let mut stack: Vec<(usize, Coord)> = vec![(0, c)]; + let mut seen: HashSet = HashSet::new(); + seen.insert(c); + + while let Some((distance, coords)) = stack.pop() { + if distance != 0 && crossroads.contains(&coords) { + graph.get_mut(&c).unwrap().insert(coords, distance); + continue; + } + + for n in coords.neighbours(map) { + if !seen.contains(&n) { + stack.push((distance + 1, n)); + seen.insert(n); + } + } + } + } + + graph +} + +fn longest_path(start: Coord, end: Coord, visited: &mut Vec, graph: &Graph) -> Option { + if start == end { + return Some(0); + } + + let mut longest: Option = None; + + visited.push(start); + + for (coord, distance) in &graph[&start] { + if !visited.contains(coord) { + longest = max(longest, longest_path(*coord, end, visited, graph).map(|v| v + distance)); + } + } + + visited.pop(); + + longest +} + +fn solve(input: &str) -> Option { + let data = parse(input); + + let starting_col = data[0].iter().position(|t| *t == Tile::Path).unwrap(); + let ending_col = data[data.len() - 1].iter().position(|t| *t == Tile::Path).unwrap(); + + let graph = build_graph( + Coord(starting_col as isize, 0), + Coord(ending_col as isize, data.len() as isize - 1), + &data); + + longest_path( + Coord(starting_col as isize, 0), + Coord(ending_col as isize, data.len() as isize - 1), + &mut vec![], + &graph + ).map(|v| v as u32) +} + +pub fn part_one(input: &str) -> Option { + solve(input) +} + +pub fn part_two(input: &str) -> Option { + let new_input = input.replace("^", ".").replace("<", ".").replace("v", ".").replace(">", "."); + solve(&new_input) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(94)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(154)); + } +}