Skip to content

Commit 9559f52

Browse files
committed
Day 17 solutions
1 parent e1235cf commit 9559f52

File tree

3 files changed

+402
-1
lines changed

3 files changed

+402
-1
lines changed

aoc-solver/src/y2023/day17.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
use std::{
2+
cmp::Ordering,
3+
collections::{BinaryHeap, HashMap},
4+
};
5+
6+
use itertools::Itertools;
7+
8+
use crate::solution::{AocError, Solution};
9+
10+
pub struct Day17;
11+
12+
type Coords = (isize, isize);
13+
type Grid = Vec<Vec<u8>>;
14+
15+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16+
enum Direction {
17+
North,
18+
East,
19+
South,
20+
West,
21+
}
22+
23+
#[derive(Clone, Eq, PartialEq)]
24+
struct Search {
25+
heat_loss: u32,
26+
position: Coords,
27+
direction: Direction,
28+
consequtive: u8,
29+
}
30+
31+
impl Ord for Search {
32+
fn cmp(&self, other: &Self) -> Ordering {
33+
other.heat_loss.cmp(&self.heat_loss)
34+
}
35+
}
36+
37+
impl PartialOrd for Search {
38+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
39+
Some(self.cmp(other))
40+
}
41+
}
42+
43+
fn parse(input: &str) -> Result<Grid, AocError> {
44+
let grid: Grid = input
45+
.trim()
46+
.lines()
47+
.map(|line| {
48+
line.chars()
49+
.map(|tile| {
50+
let heat_loss = tile
51+
.to_digit(10)
52+
.ok_or(AocError::parse(tile, "Invalid tile"))?;
53+
54+
Ok(heat_loss as u8)
55+
})
56+
.try_collect()
57+
})
58+
.try_collect()?;
59+
60+
Ok(grid)
61+
}
62+
63+
fn directions(
64+
direction: &Direction,
65+
consequtive: u8,
66+
min_consequtive: u8,
67+
) -> Vec<(Direction, isize, isize)> {
68+
if consequtive < min_consequtive {
69+
return match direction {
70+
Direction::North => vec![(Direction::North, 0, -1)],
71+
Direction::East => vec![(Direction::East, 1, 0)],
72+
Direction::South => vec![(Direction::South, 0, 1)],
73+
Direction::West => vec![(Direction::West, -1, 0)],
74+
};
75+
}
76+
77+
match direction {
78+
Direction::North => vec![
79+
(Direction::North, 0, -1),
80+
(Direction::East, 1, 0),
81+
(Direction::West, -1, 0),
82+
],
83+
Direction::East => vec![
84+
(Direction::East, 1, 0),
85+
(Direction::North, 0, -1),
86+
(Direction::South, 0, 1),
87+
],
88+
Direction::South => vec![
89+
(Direction::South, 0, 1),
90+
(Direction::East, 1, 0),
91+
(Direction::West, -1, 0),
92+
],
93+
Direction::West => vec![
94+
(Direction::West, -1, 0),
95+
(Direction::North, 0, -1),
96+
(Direction::South, 0, 1),
97+
],
98+
}
99+
}
100+
101+
fn dijkstra(grid: &Grid, min_consequtive: u8, max_consequtive: u8) -> Option<u32> {
102+
let mut heat_losses: HashMap<(Coords, Direction, u8), u32> = HashMap::new();
103+
let mut heap: BinaryHeap<Search> = BinaryHeap::new();
104+
105+
let height = grid.len();
106+
let width = grid[0].len();
107+
let target = ((width - 1) as isize, (height - 1) as isize);
108+
109+
heap.push(Search {
110+
position: (0, 0),
111+
direction: Direction::East,
112+
consequtive: 0,
113+
heat_loss: 0,
114+
});
115+
116+
while let Some(Search {
117+
position,
118+
direction,
119+
consequtive,
120+
heat_loss,
121+
}) = heap.pop()
122+
{
123+
if position == target && consequtive >= min_consequtive {
124+
return Some(heat_losses[&(position, direction, consequtive)]);
125+
}
126+
127+
if heat_loss
128+
> *heat_losses
129+
.get(&(position, direction, consequtive))
130+
.unwrap_or(&u32::MAX)
131+
{
132+
continue;
133+
}
134+
135+
for (next_direction, dx, dy) in directions(&direction, consequtive, min_consequtive) {
136+
let x = position.0 + dx;
137+
let y = position.1 + dy;
138+
139+
if x >= 0 && y >= 0 && (x as usize) < width && (y as usize) < height {
140+
let neighbour = grid[y as usize][x as usize];
141+
142+
let next_consequtive = if direction == next_direction {
143+
consequtive + 1
144+
} else {
145+
1
146+
};
147+
148+
if next_consequtive > max_consequtive {
149+
continue;
150+
}
151+
152+
let next = Search {
153+
position: (x, y),
154+
direction: next_direction,
155+
heat_loss: heat_loss + neighbour as u32,
156+
consequtive: next_consequtive,
157+
};
158+
159+
let best_known = heat_losses
160+
.entry((next.position, next.direction, next.consequtive))
161+
.or_insert(u32::MAX);
162+
163+
if next.heat_loss < *best_known {
164+
*best_known = next.heat_loss;
165+
heap.push(next)
166+
}
167+
}
168+
}
169+
}
170+
171+
None
172+
}
173+
174+
impl Solution for Day17 {
175+
type A = u32;
176+
type B = u32;
177+
178+
fn default_input(&self) -> &'static str {
179+
include_str!("../../../inputs/2023/day17.txt")
180+
}
181+
182+
fn part_1(&self, input: &str) -> Result<u32, AocError> {
183+
let grid = parse(input)?;
184+
let total_heat_loss = dijkstra(&grid, 0, 3).ok_or(AocError::logic("No path found"))?;
185+
186+
Ok(total_heat_loss)
187+
}
188+
189+
fn part_2(&self, input: &str) -> Result<u32, AocError> {
190+
let grid = parse(input)?;
191+
let total_heat_loss = dijkstra(&grid, 4, 10).ok_or(AocError::logic("No path found"))?;
192+
193+
Ok(total_heat_loss)
194+
}
195+
}
196+
197+
#[cfg(test)]
198+
mod tests {
199+
use super::*;
200+
201+
#[test]
202+
fn it_solves_part1_example() {
203+
assert_eq!(
204+
Day17.part_1(
205+
"2413432311323\n\
206+
3215453535623\n\
207+
3255245654254\n\
208+
3446585845452\n\
209+
4546657867536\n\
210+
1438598798454\n\
211+
4457876987766\n\
212+
3637877979653\n\
213+
4654967986887\n\
214+
4564679986453\n\
215+
1224686865563\n\
216+
2546548887735\n\
217+
4322674655533\n"
218+
),
219+
Ok(102)
220+
);
221+
}
222+
223+
#[test]
224+
fn it_solves_part2_example_1() {
225+
assert_eq!(
226+
Day17.part_2(
227+
"2413432311323\n\
228+
3215453535623\n\
229+
3255245654254\n\
230+
3446585845452\n\
231+
4546657867536\n\
232+
1438598798454\n\
233+
4457876987766\n\
234+
3637877979653\n\
235+
4654967986887\n\
236+
4564679986453\n\
237+
1224686865563\n\
238+
2546548887735\n\
239+
4322674655533\n"
240+
),
241+
Ok(94)
242+
);
243+
}
244+
245+
#[test]
246+
fn it_solves_part2_example_2() {
247+
assert_eq!(
248+
Day17.part_2(
249+
"111111111111\n\
250+
999999999991\n\
251+
999999999991\n\
252+
999999999991\n\
253+
999999999991\n"
254+
),
255+
Ok(71)
256+
);
257+
}
258+
}

aoc-solver/src/y2023/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ pub mod day13;
1616
pub mod day14;
1717
pub mod day15;
1818
pub mod day16;
19+
pub mod day17;
1920

20-
pub const MAX_DAYS: u8 = 16;
21+
pub const MAX_DAYS: u8 = 17;
2122

2223
pub struct Y2023;
2324

@@ -40,6 +41,7 @@ impl Solver for Y2023 {
4041
14 => day14::Day14.run(input, 14, 2023),
4142
15 => day15::Day15.run(input, 15, 2023),
4243
16 => day16::Day16.run(input, 16, 2023),
44+
17 => day17::Day17.run(input, 17, 2023),
4345
_ => vec![String::from("Solution not implemented (yet?)")],
4446
}
4547
}

0 commit comments

Comments
 (0)