Skip to content

Commit cbe5199

Browse files
committed
Day 18 solutions using A-star bruteforce
1 parent 94e39a6 commit cbe5199

File tree

5 files changed

+3659
-4
lines changed

5 files changed

+3659
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,4 @@ This should start the server at `localhost:8080`.
103103
❄️ [Day 15](aoc-solver/src/y2024/day15.rs)
104104
❄️ [Day 16](aoc-solver/src/y2024/day16.rs)
105105
❄️ [Day 17](aoc-solver/src/y2024/day17.rs)
106+
❄️ [Day 18](aoc-solver/src/y2024/day18.rs)

aoc-solver/src/y2024/day18.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
use std::{
2+
cmp::Ordering,
3+
collections::{BinaryHeap, HashMap, HashSet},
4+
};
5+
6+
use itertools::Itertools;
7+
8+
use crate::solution::{AocError, Solution};
9+
10+
type Coords = (usize, usize);
11+
12+
const DIRECTIONS: [(isize, isize); 4] = [(0, -1), (1, 0), (0, 1), (-1, 0)];
13+
14+
#[derive(Clone, Eq, PartialEq)]
15+
struct Search {
16+
steps: u32,
17+
position: Coords,
18+
}
19+
20+
impl Ord for Search {
21+
fn cmp(&self, other: &Self) -> Ordering {
22+
other.steps.cmp(&self.steps)
23+
}
24+
}
25+
26+
impl PartialOrd for Search {
27+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
28+
Some(self.cmp(other))
29+
}
30+
}
31+
32+
fn parse(input: &str) -> Result<Vec<Coords>, AocError> {
33+
let bytes = input
34+
.lines()
35+
.map(|line| {
36+
line.split_once(",")
37+
.and_then(|(x, y)| {
38+
let pos = (x.parse::<usize>().ok()?, y.parse::<usize>().ok()?);
39+
40+
Some(pos)
41+
})
42+
.ok_or_else(|| AocError::parse(input, "Invalid input"))
43+
})
44+
.try_collect()?;
45+
46+
Ok(bytes)
47+
}
48+
49+
// Manhattan distance as the heuristic function
50+
fn heuristic(a: Coords, b: Coords) -> u32 {
51+
(a.0.abs_diff(b.0) + a.1.abs_diff(b.1)) as u32
52+
}
53+
54+
pub fn a_star(corrupted: &[Coords], start: Coords, end: Coords) -> Option<u32> {
55+
let corrupted: HashSet<&Coords> = corrupted.iter().collect();
56+
let width = end.0 as isize + 1;
57+
let height = end.1 as isize + 1;
58+
59+
if corrupted.contains(&start) || corrupted.contains(&end) {
60+
return None;
61+
}
62+
63+
let mut g = HashMap::new();
64+
let mut f = HashMap::new();
65+
let mut open_set = BinaryHeap::new();
66+
let mut closed_set = HashSet::new();
67+
68+
g.insert(start, 0);
69+
f.insert(start, heuristic(start, end));
70+
71+
open_set.push(Search {
72+
steps: f[&start],
73+
position: start,
74+
});
75+
76+
while let Some(Search { position, .. }) = open_set.pop() {
77+
if position == end {
78+
return g.get(&position).cloned();
79+
}
80+
81+
if closed_set.contains(&position) {
82+
continue;
83+
}
84+
85+
closed_set.insert(position);
86+
87+
for (dx, dy) in DIRECTIONS {
88+
let (x, y) = (position.0 as isize + dx, position.1 as isize + dy);
89+
if x < 0 || y < 0 || x >= width || y >= height {
90+
continue;
91+
}
92+
93+
let neighbour = (x as usize, y as usize);
94+
95+
if corrupted.contains(&neighbour) || closed_set.contains(&neighbour) {
96+
continue;
97+
}
98+
99+
let tentative_g = g.entry(position).or_default().saturating_add(1);
100+
let existing_g = g.get(&neighbour).unwrap_or(&u32::MAX);
101+
102+
if tentative_g < *existing_g {
103+
g.insert(neighbour, tentative_g);
104+
f.insert(neighbour, tentative_g + heuristic(neighbour, end));
105+
106+
open_set.push(Search {
107+
steps: tentative_g + heuristic(neighbour, end),
108+
position: neighbour,
109+
});
110+
}
111+
}
112+
}
113+
114+
None
115+
}
116+
117+
pub struct Day18;
118+
impl Solution for Day18 {
119+
type A = u32;
120+
type B = String;
121+
122+
fn default_input(&self) -> &'static str {
123+
include_str!("../../../inputs/2024/day18.txt")
124+
}
125+
126+
fn part_1(&self, input: &str) -> Result<u32, AocError> {
127+
let corrupted = parse(input)?;
128+
129+
let start = (0, 0);
130+
let end = (70, 70);
131+
let min_distance = a_star(&corrupted[0..1024], start, end)
132+
.ok_or_else(|| AocError::logic("No path found"))?;
133+
134+
Ok(min_distance)
135+
}
136+
137+
fn part_2(&self, input: &str) -> Result<String, AocError> {
138+
let corrupted = parse(input)?;
139+
140+
let mut t = 1024;
141+
let start = (0, 0);
142+
let end = (70, 70);
143+
144+
while a_star(&corrupted[0..t], start, end).is_some() {
145+
t += 1;
146+
}
147+
148+
Ok(format!("{},{}", corrupted[t - 1].0, corrupted[t - 1].1))
149+
}
150+
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use super::*;
155+
156+
const TEST_INPUT: &str = "5,4\n\
157+
4,2\n\
158+
4,5\n\
159+
3,0\n\
160+
2,1\n\
161+
6,3\n\
162+
2,4\n\
163+
1,5\n\
164+
0,6\n\
165+
3,3\n\
166+
2,6\n\
167+
5,1\n\
168+
1,2\n\
169+
5,5\n\
170+
2,5\n\
171+
6,5\n\
172+
1,4\n\
173+
0,4\n\
174+
6,4\n\
175+
1,1\n\
176+
6,1\n\
177+
1,0\n\
178+
0,5\n\
179+
1,6\n\
180+
2,0";
181+
182+
#[test]
183+
fn it_solves_part1_example() {
184+
let bytes = parse(TEST_INPUT).unwrap();
185+
186+
let min_distance = a_star(&bytes[0..12], (0, 0), (6, 6));
187+
188+
assert_eq!(min_distance, Some(22));
189+
}
190+
191+
#[test]
192+
fn it_solves_part2_example() {
193+
let bytes = parse(TEST_INPUT).unwrap();
194+
195+
let mut t = 12;
196+
197+
while a_star(&bytes[0..t], (0, 0), (6, 6)).is_some() {
198+
t += 1;
199+
}
200+
201+
assert_eq!(t, 21);
202+
}
203+
}

aoc-solver/src/y2024/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub mod day14;
1717
pub mod day15;
1818
pub mod day16;
1919
pub mod day17;
20-
// pub mod day18;
20+
pub mod day18;
2121
// pub mod day19;
2222
// pub mod day20;
2323
// pub mod day21;
@@ -26,7 +26,7 @@ pub mod day17;
2626
// pub mod day24;
2727
// pub mod day25;
2828

29-
pub const MAX_DAYS: u8 = 17;
29+
pub const MAX_DAYS: u8 = 18;
3030

3131
pub struct Y2024;
3232

@@ -50,7 +50,7 @@ impl Solver for Y2024 {
5050
15 => day15::Day15.run(input, 15, 2024),
5151
16 => day16::Day16.run(input, 16, 2024),
5252
17 => day17::Day17.run(input, 17, 2024),
53-
// 18 => day18::Day18.run(input, 18, 2024),
53+
18 => day18::Day18.run(input, 18, 2024),
5454
// 19 => day19::Day19.run(input, 19, 2024),
5555
// 20 => day20::Day20.run(input, 20, 2024),
5656
// 21 => day21::Day21.run(input, 21, 2024),
@@ -92,7 +92,7 @@ impl Solver for Y2024 {
9292
15 => include_str!("./day15.rs"),
9393
16 => include_str!("./day16.rs"),
9494
17 => include_str!("./day17.rs"),
95-
// 18 => include_str!("./day18.rs"),
95+
18 => include_str!("./day18.rs"),
9696
// 19 => include_str!("./day19.rs"),
9797
// 20 => include_str!("./day20.rs"),
9898
// 21 => include_str!("./day21.rs"),

aoc-web/src/header.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub fn header(props: &HeaderProps) -> Html {
3838
<NavLink route={Route::WarehouseRobot} current={props.route.clone()} text={"15+"}/>
3939
<NavLink route={Route::Solution { year: 2024, day: 16 }} current={props.route.clone()} text={"16"}/>
4040
<NavLink route={Route::Solution { year: 2024, day: 17 }} current={props.route.clone()} text={"17"}/>
41+
<NavLink route={Route::Solution { year: 2024, day: 18 }} current={props.route.clone()} text={"18"}/>
4142
</>
4243
}
4344
},

0 commit comments

Comments
 (0)