|
| 1 | +use std::collections::{HashMap, HashSet}; |
| 2 | + |
| 3 | +use aoc_derive::aoc_main; |
| 4 | +use graphs::{WeightedGraph, dijkstra}; |
| 5 | +use grid::Grid; |
| 6 | +use itertools::Itertools; |
| 7 | +use math::Vec2D; |
| 8 | +use utils::*; |
| 9 | + |
| 10 | +#[derive(Debug, Clone, derive_more::Into, derive_more::Deref)] |
| 11 | +struct Maze(Grid<char>); |
| 12 | + |
| 13 | +impl WeightedGraph for Maze { |
| 14 | + type Node = (Vec2D, Vec2D); |
| 15 | + |
| 16 | + fn neighbors<'a, 'b: 'a>( |
| 17 | + &'a self, |
| 18 | + (pos, heading): &'b Self::Node, |
| 19 | + ) -> impl Iterator<Item = (Self::Node, graphs::Cost)> + 'a { |
| 20 | + self.orthogonal_neighbors(pos).filter_map(|neighbor| { |
| 21 | + (self[neighbor] != '#').then_some(( |
| 22 | + (neighbor, *pos - neighbor), |
| 23 | + 1 + if *pos - neighbor == *heading { 0 } else { 1000 }, |
| 24 | + )) |
| 25 | + }) |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +fn all_paths( |
| 30 | + maze: &Maze, |
| 31 | + pos: Vec2D, |
| 32 | + heading: Vec2D, |
| 33 | + score: usize, |
| 34 | + max_score: usize, |
| 35 | + mut path: HashSet<Vec2D>, |
| 36 | + visited: &mut HashMap<(Vec2D, Vec2D), usize>, |
| 37 | +) -> HashSet<Vec2D> { |
| 38 | + // This is the main optimization that makes DFS work here: |
| 39 | + // If we already visited this node with a lower score, stop the search |
| 40 | + if let Some(&prev_score) = visited.get(&(pos, heading)) { |
| 41 | + if prev_score < score { |
| 42 | + return HashSet::new(); |
| 43 | + } |
| 44 | + } |
| 45 | + visited.insert((pos, heading), score); |
| 46 | + |
| 47 | + // Stop if we already have a higher cost than the optimal solution |
| 48 | + if score > max_score { |
| 49 | + return HashSet::new(); |
| 50 | + } |
| 51 | + |
| 52 | + path.insert(pos); |
| 53 | + if maze[pos] == 'E' { |
| 54 | + return path; |
| 55 | + } |
| 56 | + |
| 57 | + maze.neighbors(&(pos, heading)) |
| 58 | + // Don't go backwards |
| 59 | + .filter(|((pos, _), _)| !path.contains(pos)) |
| 60 | + // Sort ascending, so that we try to go forward before doing a turn |
| 61 | + .sorted_by_key(|((_, _), new_score)| *new_score) |
| 62 | + .flat_map(|((neighbor, new_heading), new_score)| { |
| 63 | + all_paths( |
| 64 | + maze, |
| 65 | + neighbor, |
| 66 | + new_heading, |
| 67 | + score + new_score, |
| 68 | + max_score, |
| 69 | + path.clone(), |
| 70 | + visited, |
| 71 | + ) |
| 72 | + }) |
| 73 | + .collect() |
| 74 | +} |
| 75 | + |
| 76 | +#[aoc_main] |
| 77 | +fn solve(input: Input) -> impl Into<Solution> { |
| 78 | + let maze = Maze(input.char_grid()); |
| 79 | + |
| 80 | + let (start, heading) = (maze.find_position(&'S').unwrap(), Vec2D::new(1, 0)); |
| 81 | + |
| 82 | + let part1 = dijkstra(&maze, [(start, heading)], |(pos, _)| maze[*pos] == 'E').unwrap(); |
| 83 | + |
| 84 | + let part2 = |
| 85 | + all_paths(&maze, start, heading, 0, part1, HashSet::new(), &mut HashMap::new()).len(); |
| 86 | + |
| 87 | + (part1, part2) |
| 88 | +} |
| 89 | + |
| 90 | +#[cfg(test)] |
| 91 | +mod tests { |
| 92 | + use super::*; |
| 93 | + #[test] |
| 94 | + fn test_examples() { |
| 95 | + use utils::assert_example; |
| 96 | + assert_example!( |
| 97 | + r#"############### |
| 98 | +#.......#....E# |
| 99 | +#.#.###.#.###.# |
| 100 | +#.....#.#...#.# |
| 101 | +#.###.#####.#.# |
| 102 | +#.#.#.......#.# |
| 103 | +#.#.#####.###.# |
| 104 | +#...........#.# |
| 105 | +###.#.#####.#.# |
| 106 | +#...#.....#.#.# |
| 107 | +#.#.#.###.#.#.# |
| 108 | +#.....#...#.#.# |
| 109 | +#.###.#.#.#.#.# |
| 110 | +#S..#.....#...# |
| 111 | +############### |
| 112 | + "#, |
| 113 | + 7036, |
| 114 | + 45 |
| 115 | + ); |
| 116 | + |
| 117 | + assert_example!( |
| 118 | + "################# |
| 119 | +#...#...#...#..E# |
| 120 | +#.#.#.#.#.#.#.#^# |
| 121 | +#.#.#.#...#...#^# |
| 122 | +#.#.#.#.###.#.#^# |
| 123 | +#>>v#.#.#.....#^# |
| 124 | +#^#v#.#.#.#####^# |
| 125 | +#^#v..#.#.#>>>>^# |
| 126 | +#^#v#####.#^###.# |
| 127 | +#^#v#..>>>>^#...# |
| 128 | +#^#v###^#####.### |
| 129 | +#^#v#>>^#.....#.# |
| 130 | +#^#v#^#####.###.# |
| 131 | +#^#v#^........#.# |
| 132 | +#^#v#^#########.# |
| 133 | +#S#>>^..........# |
| 134 | +#################", |
| 135 | + 11048, |
| 136 | + 64 |
| 137 | + ); |
| 138 | + } |
| 139 | +} |
0 commit comments