-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday17.rs
141 lines (123 loc) · 4 KB
/
day17.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use std::collections::{HashMap, VecDeque};
use aoc_lib::{
answer::Answer,
directions::{Cardinal, Direction},
matrix::Matrix,
solution::Solution,
vec2::Vec2,
};
pub struct Day17;
impl Solution for Day17 {
fn part_a(&self, input: &[String]) -> Answer {
let grid = parse(input);
grid.minimum_path_heat_loss(1, 3).into()
}
fn part_b(&self, input: &[String]) -> Answer {
let grid = parse(input);
grid.minimum_path_heat_loss(4, 10).into()
}
}
struct Grid {
grid: Matrix<usize>,
}
impl Grid {
fn minimum_path_heat_loss(&self, min_dist: usize, max_dist: usize) -> usize {
let mut queue = VecDeque::new();
let mut seen: HashMap<Node, usize> = HashMap::new();
let start = Vec2::new(0, 0);
let end = Vec2::new(self.grid.cols - 1, self.grid.rows - 1);
for direction in [Cardinal::East, Cardinal::South] {
let node = Node::new(start, direction, 1);
queue.push_back((node, 0));
seen.insert(node, 0);
}
let mut min = usize::MAX;
while let Some((node, cost)) = queue.pop_front() {
// arrived at the end
if node.pos == end && node.turn_counter >= min_dist {
min = min.min(cost);
continue;
}
// explore all (avaibles) neighboors
for dir in Grid::avaible_directions(&self.grid, &node, min_dist, max_dist) {
let next_pos =
Vec2::<usize>::try_from(Vec2::<isize>::from(&node.pos) + dir.to_offset())
.unwrap();
let counter = if dir == node.facing {
node.turn_counter + 1
} else {
1
};
let next_node = Node::new(next_pos, dir, counter);
let cost = cost + self.grid[next_node.pos];
if !seen.contains_key(&next_node) || *seen.get(&next_node).unwrap() > cost {
queue.push_back((next_node, cost));
seen.insert(next_node, cost);
}
}
}
min
}
fn avaible_directions(
grid: &Matrix<usize>,
curr: &Node,
min_dist: usize,
max_dist: usize,
) -> Vec<Cardinal> {
// at each state, you can continue straight, go left or right.
// if the turn distance is 0 you must turn left or right and the straight
// option will not be avaible
let mut avaibles = vec![];
if curr.turn_counter < max_dist {
avaibles.push(curr.facing);
}
if curr.turn_counter >= min_dist {
avaibles.push(curr.facing.turn_right());
avaibles.push(curr.facing.turn_left());
}
avaibles
.into_iter()
.filter(|d| grid.contains(&(Vec2::<isize>::from(curr.pos) + d.to_offset())))
.collect()
}
}
fn parse(input: &[String]) -> Grid {
Grid {
grid: Matrix::from_chars(input)
.map_to(|c: char| c.to_digit(10).unwrap_or_default() as usize),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct Node {
pos: Vec2<usize>,
facing: Cardinal,
turn_counter: usize,
}
impl Node {
fn new(pos: Vec2<usize>, facing: Cardinal, turn_counter: usize) -> Self {
Self {
pos,
facing,
turn_counter,
}
}
}
#[cfg(test)]
mod test {
use aoc_lib::{self, answer::Answer, input, solution::Solution};
use super::Day17;
#[test]
fn test_a() {
let input =
input::read_file(&format!("{}day_17_test.txt", crate::FILES_PREFIX_TEST)).unwrap();
let answer = Day17.part_a(&input);
assert_eq!(<i32 as Into<Answer>>::into(102), answer);
}
#[test]
fn test_b() {
let input =
input::read_file(&format!("{}day_17_test.txt", crate::FILES_PREFIX_TEST)).unwrap();
let answer = Day17.part_b(&input);
assert_eq!(<i32 as Into<Answer>>::into(94), answer);
}
}