Skip to content

Commit 1cdfc8e

Browse files
committed
Day 16
1 parent e4b545d commit 1cdfc8e

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

data/examples/16-1.txt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
###############
2+
#.......#....E#
3+
#.#.###.#.###.#
4+
#.....#.#...#.#
5+
#.###.#####.#.#
6+
#.#.#.......#.#
7+
#.#.#####.###.#
8+
#...........#.#
9+
###.#.#####.#.#
10+
#...#.....#.#.#
11+
#.#.#.###.#.#.#
12+
#.....#...#.#.#
13+
#.###.#.#.#.#.#
14+
#S..#.....#...#
15+
###############

data/examples/16-2.txt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#################
2+
#...#...#...#..E#
3+
#.#.#.#.#.#.#.#.#
4+
#.#.#.#...#...#.#
5+
#.#.#.#.###.#.#.#
6+
#...#.#.#.....#.#
7+
#.#.#.#.#.#####.#
8+
#.#...#.#.#.....#
9+
#.#.#####.#.###.#
10+
#.#.#.......#...#
11+
#.#.###.#####.###
12+
#.#.#...#.....#.#
13+
#.#.#.#####.###.#
14+
#.#.#.........#.#
15+
#.#.#.#########.#
16+
#S#.............#
17+
#################

src/bin/16.rs

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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

Comments
 (0)