|
| 1 | +use itertools::Itertools; |
| 2 | +use std::cmp::Ordering; |
| 3 | +use std::collections::{BinaryHeap, HashMap, HashSet}; |
| 4 | + |
| 5 | +advent_of_code::solution!(16); |
| 6 | + |
| 7 | +const DIRECTIONS: [(isize, isize); 4] = [ |
| 8 | + (0, -1), |
| 9 | + (1, 0), |
| 10 | + (0, 1), |
| 11 | + (-1, 0), |
| 12 | +]; |
| 13 | + |
| 14 | +#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] |
| 15 | +struct Node(usize, usize, NodeType); |
| 16 | + |
| 17 | +#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] |
| 18 | +enum NodeType { |
| 19 | + Empty, |
| 20 | + Wall, |
| 21 | +} |
| 22 | + |
| 23 | +#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] |
| 24 | +struct State { |
| 25 | + node: Node, |
| 26 | + cost: u32, |
| 27 | + direction: (isize, isize), |
| 28 | +} |
| 29 | + |
| 30 | +impl Ord for State { |
| 31 | + fn cmp(&self, other: &Self) -> Ordering { |
| 32 | + other.cost.cmp(&self.cost) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +impl PartialOrd for State { |
| 37 | + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| 38 | + Some(self.cmp(other)) |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +fn parse(input: &str) -> (Node, Node, Vec<Vec<Node>>) { |
| 43 | + let (mut source_node, mut target_node) = (Node(0, 0, NodeType::Empty), Node(0, 0, NodeType::Empty)); |
| 44 | + let nodes = input.lines() |
| 45 | + .enumerate() |
| 46 | + .map(|(y, line)| { |
| 47 | + let mut local_source_node = None; |
| 48 | + let mut local_target_node = None; |
| 49 | + let nodes = line.char_indices().map(|(x, char)| { |
| 50 | + match char { |
| 51 | + '#' => Node(x, y, NodeType::Wall), |
| 52 | + _ => { |
| 53 | + let node = Node(x, y, NodeType::Empty); |
| 54 | + if char == 'S' { |
| 55 | + local_source_node = Some(node); |
| 56 | + } else if char == 'E' { |
| 57 | + local_target_node = Some(node); |
| 58 | + } |
| 59 | + node |
| 60 | + }, |
| 61 | + } |
| 62 | + }).collect_vec(); |
| 63 | + if let Some(node) = local_source_node { |
| 64 | + source_node = node; |
| 65 | + } |
| 66 | + if let Some(node) = local_target_node { |
| 67 | + target_node = node; |
| 68 | + } |
| 69 | + nodes |
| 70 | + }) |
| 71 | + .collect_vec(); |
| 72 | + (source_node, target_node, nodes) |
| 73 | +} |
| 74 | + |
| 75 | +fn find_shortest_path(source: Node, target: Node, nodes: Vec<Vec<Node>>) -> Option<u32> { |
| 76 | + let mut dist = HashMap::new(); |
| 77 | + let mut heap = BinaryHeap::new(); |
| 78 | + |
| 79 | + dist.insert(source, 0); |
| 80 | + heap.push(State { node: source, cost: 0, direction: (1, 0) }); |
| 81 | + |
| 82 | + while let Some(State { node, cost, direction }) = heap.pop() { |
| 83 | + if node == target { |
| 84 | + return Some(cost); |
| 85 | + } |
| 86 | + |
| 87 | + if cost > *dist.get(&node).unwrap_or(&u32::MAX) { |
| 88 | + continue; |
| 89 | + } |
| 90 | + |
| 91 | + for new_direction in &DIRECTIONS { |
| 92 | + let Some(neighbor) = offset(&(node.0, node.1), new_direction) |
| 93 | + .map(|(x, y)| nodes[y][x]) |
| 94 | + .filter(|node| node.2 != NodeType::Wall) else { continue }; |
| 95 | + |
| 96 | + let new_cost = cost + 1 + (1000 * u32::from(*new_direction != direction)); |
| 97 | + let next = State { node: neighbor, cost: new_cost, direction: *new_direction }; |
| 98 | + |
| 99 | + if next.cost < *dist.get(&next.node).unwrap_or(&u32::MAX) { |
| 100 | + heap.push(next); |
| 101 | + dist.insert(next.node, next.cost); |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + None |
| 107 | +} |
| 108 | + |
| 109 | +fn find_all_shortest_paths(source: Node, target: Node, nodes: Vec<Vec<Node>>) -> Option<u32> { |
| 110 | + let mut dist = HashMap::new(); |
| 111 | + let mut heap = BinaryHeap::new(); |
| 112 | + let mut paths: HashMap<_, Vec<_>> = HashMap::new(); |
| 113 | + |
| 114 | + dist.insert((source, (1, 0)), 0); |
| 115 | + dist.insert((source, (0, -1)), 1000); |
| 116 | + heap.push(State { node: source, cost: 0, direction: (1, 0) }); |
| 117 | + heap.push(State { node: source, cost: 1000, direction: (0, -1) }); |
| 118 | + |
| 119 | + while let Some(State { node, cost, direction }) = heap.pop() { |
| 120 | + if node == target { |
| 121 | + break; |
| 122 | + } |
| 123 | + |
| 124 | + if cost > *dist.get(&(node, direction)).unwrap_or(&u32::MAX) { |
| 125 | + continue; |
| 126 | + } |
| 127 | + |
| 128 | + let Some(neighbor) = offset(&(node.0, node.1), &direction) |
| 129 | + .map(|(x, y)| nodes[y][x]) |
| 130 | + .filter(|node| node.2 != NodeType::Wall) else { continue }; |
| 131 | + |
| 132 | + for new_direction in &DIRECTIONS { |
| 133 | + if *new_direction == (-direction.0, -direction.1) { |
| 134 | + continue; |
| 135 | + } |
| 136 | + let new_cost = cost + 1 + (1000 * u32::from(*new_direction != direction)); |
| 137 | + let next = State { node: neighbor, cost: new_cost, direction: *new_direction }; |
| 138 | + |
| 139 | + match next.cost.cmp(dist.get(&(next.node, *new_direction)).unwrap_or(&u32::MAX)) { |
| 140 | + Ordering::Less => { |
| 141 | + heap.push(next); |
| 142 | + dist.insert((next.node, next.direction), next.cost); |
| 143 | + paths.insert((next.node, next.direction), vec![(node, direction)]); |
| 144 | + } |
| 145 | + Ordering::Equal => paths.entry((next.node, next.direction)).or_default().push((node, direction)), |
| 146 | + _ => {} |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + let mut best_path_nodes = HashSet::new(); |
| 152 | + if !dist.contains_key(&(target, (1, 0))) && !dist.contains_key(&(target, (0, -1))) { |
| 153 | + return None; |
| 154 | + } |
| 155 | + |
| 156 | + let mut stack = vec![(target, (1, 0))]; |
| 157 | + if dist.contains_key(&(target, (0, -1))) { |
| 158 | + stack.push((target, (0, -1))); |
| 159 | + } |
| 160 | + |
| 161 | + while let Some((node, direction)) = stack.pop() { |
| 162 | + best_path_nodes.insert(node); |
| 163 | + let Some(prev_nodes) = paths.get(&(node, direction)) else { continue }; |
| 164 | + for &(prev_node, prev_direction) in prev_nodes { |
| 165 | + stack.push((prev_node, prev_direction)); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + Some(best_path_nodes.len() as u32) |
| 170 | +} |
| 171 | + |
| 172 | +fn offset(pos: &(usize, usize), direction: &(isize, isize)) -> Option<(usize, usize)> { |
| 173 | + let x = pos.0.checked_add_signed(direction.0)?; |
| 174 | + let y = pos.1.checked_add_signed(direction.1)?; |
| 175 | + Some((x, y)) |
| 176 | +} |
| 177 | + |
| 178 | +pub fn part_one(input: &str) -> Option<u32> { |
| 179 | + let (source_node, target_node, nodes) = parse(input); |
| 180 | + find_shortest_path(source_node, target_node, nodes) |
| 181 | +} |
| 182 | + |
| 183 | +pub fn part_two(input: &str) -> Option<u32> { |
| 184 | + let (source_node, target_node, nodes) = parse(input); |
| 185 | + find_all_shortest_paths(source_node, target_node, nodes) |
| 186 | +} |
| 187 | + |
| 188 | +#[cfg(test)] |
| 189 | +mod tests { |
| 190 | + use super::*; |
| 191 | + |
| 192 | + #[test] |
| 193 | + fn test_part_one() { |
| 194 | + let result = part_one(&advent_of_code::template::read_file_part("examples", DAY, 1)); |
| 195 | + assert_eq!(result, Some(7036)); |
| 196 | + let result = part_one(&advent_of_code::template::read_file_part("examples", DAY, 2)); |
| 197 | + assert_eq!(result, Some(11048)); |
| 198 | + } |
| 199 | + |
| 200 | + #[test] |
| 201 | + fn test_part_two() { |
| 202 | + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 1)); |
| 203 | + assert_eq!(result, Some(45)); |
| 204 | + let result = part_two(&advent_of_code::template::read_file_part("examples", DAY, 2)); |
| 205 | + assert_eq!(result, Some(64)); |
| 206 | + } |
| 207 | +} |
0 commit comments