Skip to content

Commit 4a536c8

Browse files
committed
Solve day24
1 parent 0a2f01b commit 4a536c8

File tree

3 files changed

+289
-1
lines changed

3 files changed

+289
-1
lines changed

src/bin/24.rs

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
use std::{collections::HashSet, fmt};
2+
3+
use advent_of_code::helpers::{lcm, Point, PointDirection, PointGrid};
4+
use parse_display::{Display, FromStr};
5+
use priority_queue::PriorityQueue;
6+
7+
#[derive(Debug, Display, FromStr, Clone, PartialEq, Eq)]
8+
#[display("{dir}")]
9+
struct Blizzard {
10+
dir: PointDirection,
11+
}
12+
13+
#[derive(Debug, Default, Clone, PartialEq, Eq)]
14+
struct BlizzardList(Vec<Blizzard>);
15+
16+
impl fmt::Display for BlizzardList {
17+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18+
match self.0.len() {
19+
0 => {
20+
write!(f, ".")
21+
}
22+
1 => {
23+
write!(f, "{}", self.0[0].dir)
24+
}
25+
_ => {
26+
write!(f, "{}", self.0.len())
27+
}
28+
}
29+
}
30+
}
31+
32+
fn parse_input(input: &str) -> (PointGrid<BlizzardList>, Point<isize>, Point<isize>) {
33+
let mut result: PointGrid<BlizzardList> = PointGrid::default();
34+
35+
for (y, l) in input.lines().enumerate().skip(1) {
36+
for (x, c) in l.chars().enumerate() {
37+
let blizzard = match c {
38+
'^' | '>' | 'v' | '<' => Some(c.to_string().parse::<Blizzard>().unwrap()),
39+
_ => None,
40+
};
41+
42+
if let Some(b) = blizzard {
43+
result
44+
.points
45+
.entry(Point {
46+
x: x as isize,
47+
y: y as isize,
48+
})
49+
.or_default()
50+
.0
51+
.push(b);
52+
} else if c == '.' {
53+
result
54+
.points
55+
.entry(Point {
56+
x: x as isize,
57+
y: y as isize,
58+
})
59+
.or_default();
60+
}
61+
}
62+
}
63+
64+
let (min, max) = result.dimensions();
65+
let start = Point {
66+
x: min.x,
67+
y: min.y - 1,
68+
};
69+
let end = Point { x: max.x, y: max.y };
70+
result.insert(start, BlizzardList::default());
71+
72+
(result, start, end)
73+
}
74+
75+
fn forward_blizzards(blizzards: &PointGrid<BlizzardList>) -> PointGrid<BlizzardList> {
76+
let mut result: PointGrid<BlizzardList> = PointGrid::default();
77+
let (min_grid, max_grid) = blizzards.dimensions();
78+
let (min_bliz, max_bliz) = (
79+
Point {
80+
x: min_grid.x,
81+
y: min_grid.y + 1,
82+
},
83+
Point {
84+
x: max_grid.x + 1,
85+
y: max_grid.y,
86+
},
87+
);
88+
for p in blizzards.points.keys() {
89+
result.points.entry(*p).or_default();
90+
}
91+
92+
for (p, bl) in blizzards.points.iter() {
93+
for b in bl.0.iter() {
94+
let new_point = p
95+
.get_point_in_direction(&b.dir, 1)
96+
.wrap_around_in_rectangle(min_bliz, max_bliz);
97+
98+
result
99+
.points
100+
.entry(new_point)
101+
.or_default()
102+
.0
103+
.push(b.clone());
104+
}
105+
}
106+
107+
result
108+
}
109+
110+
fn bfs(
111+
bl_cache: &[PointGrid<BlizzardList>],
112+
start: &Point<isize>,
113+
end: &Point<isize>,
114+
time_offset: usize,
115+
cycle_length: usize,
116+
max_distance: usize,
117+
) -> Option<usize> {
118+
let mut closed_set: HashSet<(Point<isize>, usize)> = HashSet::new();
119+
let mut queue: PriorityQueue<(Point<isize>, usize), usize> = PriorityQueue::new();
120+
121+
queue.push(
122+
(*start, time_offset),
123+
max_distance - start.manhattan_distance(end),
124+
);
125+
while let Some(((current_pos, current_min), _)) = queue.pop() {
126+
closed_set.insert((current_pos, current_min % cycle_length));
127+
128+
for next_pos in
129+
get_possible_actions(&bl_cache[(current_min + 1) % cycle_length], &current_pos)
130+
{
131+
if closed_set.contains(&(next_pos, (current_min + 1) % cycle_length)) {
132+
continue;
133+
}
134+
if next_pos == *end {
135+
return Some(current_min + 1);
136+
}
137+
queue.push(
138+
(next_pos, current_min + 1),
139+
max_distance - (current_min + 1 + current_pos.manhattan_distance(end)),
140+
);
141+
}
142+
}
143+
144+
None
145+
}
146+
147+
fn get_possible_actions(
148+
blizzards_next_round: &PointGrid<BlizzardList>,
149+
current_position: &Point<isize>,
150+
) -> Vec<Point<isize>> {
151+
let mut next_positions = vec![];
152+
153+
for d in PointDirection::all() {
154+
let new_pos = current_position.get_point_in_direction(d, 1);
155+
if let Some(bl) = blizzards_next_round.points.get(&new_pos) {
156+
if bl.0.is_empty() {
157+
next_positions.push(new_pos);
158+
}
159+
}
160+
}
161+
162+
// wait
163+
let new_pos = *current_position;
164+
if let Some(bl) = blizzards_next_round.points.get(&new_pos) {
165+
if bl.0.is_empty() {
166+
next_positions.push(new_pos);
167+
}
168+
}
169+
170+
next_positions
171+
}
172+
173+
fn init_valley(
174+
blizzards: &PointGrid<BlizzardList>,
175+
) -> (Vec<PointGrid<BlizzardList>>, usize, usize) {
176+
let (min, max) = blizzards.dimensions();
177+
let cycle_length = lcm((max.x + 1 - min.x) as usize, (max.y - (min.y + 1)) as usize);
178+
let max_distance = Point {
179+
x: max.x + 1,
180+
y: max.y + 1,
181+
}
182+
.manhattan_distance(&min)
183+
* cycle_length;
184+
185+
let mut bl_cache: Vec<PointGrid<BlizzardList>> = vec![];
186+
187+
let mut current_bliz = blizzards.clone();
188+
for _ in 0..cycle_length {
189+
bl_cache.push(current_bliz.clone());
190+
current_bliz = forward_blizzards(&current_bliz);
191+
}
192+
193+
(bl_cache, cycle_length, max_distance)
194+
}
195+
196+
pub fn part_one(_input: &str) -> Option<usize> {
197+
let (grid, start, end) = parse_input(_input);
198+
let (bl_cache, cycle_length, max_distance) = init_valley(&grid);
199+
200+
bfs(&bl_cache, &start, &end, 0, cycle_length, max_distance)
201+
}
202+
203+
pub fn part_two(_input: &str) -> Option<usize> {
204+
let (grid, start, end) = parse_input(_input);
205+
let (bl_cache, cycle_length, max_distance) = init_valley(&grid);
206+
207+
let goal1 = bfs(&bl_cache, &start, &end, 0, cycle_length, max_distance).unwrap();
208+
let goal2 = bfs(&bl_cache, &end, &start, goal1, cycle_length, max_distance).unwrap();
209+
bfs(&bl_cache, &start, &end, goal2, cycle_length, max_distance)
210+
}
211+
212+
fn main() {
213+
let input = &advent_of_code::read_file("inputs", 24);
214+
advent_of_code::solve!(1, part_one, input);
215+
advent_of_code::solve!(2, part_two, input);
216+
}
217+
218+
#[cfg(test)]
219+
mod tests {
220+
use super::*;
221+
222+
#[test]
223+
fn test_part_one() {
224+
let input = advent_of_code::read_file("examples", 24);
225+
assert_eq!(part_one(&input), Some(18));
226+
}
227+
228+
#[test]
229+
fn test_part_two() {
230+
let input = advent_of_code::read_file("examples", 24);
231+
assert_eq!(part_two(&input), Some(54));
232+
}
233+
}

src/examples/24.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#.######
2+
#>>.<^<#
3+
#.<..<<#
4+
#>v.><>#
5+
#<^v^^>#
6+
######.#

src/helpers.rs

+50-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use parse_display::{Display, FromStr};
1111
* Example import from this file: `use advent_of_code::helpers::example_fn;`.
1212
*/
1313

14-
#[derive(Debug, Clone, Display, FromStr)]
14+
#[derive(Debug, Clone, Display, FromStr, PartialEq, Eq)]
1515
pub enum PointDirection {
1616
#[display("^")]
1717
North,
@@ -109,6 +109,10 @@ impl<T: Sub<Output = T>> Sub for Point<T> {
109109
}
110110

111111
impl Point<isize> {
112+
pub fn manhattan_distance(&self, other: &Self) -> usize {
113+
self.x.abs_diff(other.x) + self.y.abs_diff(other.y)
114+
}
115+
112116
pub fn get_point_in_direction(&self, direction: &PointDirection, distance: isize) -> Self {
113117
match direction {
114118
PointDirection::North => Self {
@@ -145,6 +149,29 @@ impl Point<isize> {
145149
},
146150
}
147151
}
152+
153+
pub fn is_in_rectangle(&self, min: Self, max: Self) -> bool {
154+
self.x >= min.x && self.x < max.x && self.y >= min.y && self.y > max.y
155+
}
156+
157+
pub fn wrap_around_in_rectangle(&self, min: Self, max: Self) -> Self {
158+
let mut new_x = self.x;
159+
let mut new_y = self.y;
160+
161+
if new_x < min.x {
162+
new_x = max.x - 1;
163+
} else if new_x >= max.x {
164+
new_x = min.x;
165+
}
166+
167+
if new_y < min.y {
168+
new_y = max.y - 1;
169+
} else if new_y >= max.y {
170+
new_y = min.y;
171+
}
172+
173+
Point { x: new_x, y: new_y }
174+
}
148175
}
149176

150177
#[derive(Debug, Clone)]
@@ -276,3 +303,25 @@ impl Point3<i32> {
276303
]
277304
}
278305
}
306+
307+
pub fn lcm(first: usize, second: usize) -> usize {
308+
first * second / gcd(first, second)
309+
}
310+
311+
pub fn gcd(first: usize, second: usize) -> usize {
312+
let mut max = first;
313+
let mut min = second;
314+
if min > max {
315+
std::mem::swap(&mut max, &mut min);
316+
}
317+
318+
loop {
319+
let res = max % min;
320+
if res == 0 {
321+
return min;
322+
}
323+
324+
max = min;
325+
min = res;
326+
}
327+
}

0 commit comments

Comments
 (0)