Skip to content

Commit

Permalink
Day 18 solutions using A-star bruteforce
Browse files Browse the repository at this point in the history
  • Loading branch information
Cadiac committed Dec 18, 2024
1 parent 94e39a6 commit cbe5199
Show file tree
Hide file tree
Showing 5 changed files with 3,659 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ This should start the server at `localhost:8080`.
❄️ [Day 15](aoc-solver/src/y2024/day15.rs)
❄️ [Day 16](aoc-solver/src/y2024/day16.rs)
❄️ [Day 17](aoc-solver/src/y2024/day17.rs)
❄️ [Day 18](aoc-solver/src/y2024/day18.rs)
203 changes: 203 additions & 0 deletions aoc-solver/src/y2024/day18.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use std::{
cmp::Ordering,
collections::{BinaryHeap, HashMap, HashSet},
};

use itertools::Itertools;

use crate::solution::{AocError, Solution};

type Coords = (usize, usize);

const DIRECTIONS: [(isize, isize); 4] = [(0, -1), (1, 0), (0, 1), (-1, 0)];

#[derive(Clone, Eq, PartialEq)]
struct Search {
steps: u32,
position: Coords,
}

impl Ord for Search {
fn cmp(&self, other: &Self) -> Ordering {
other.steps.cmp(&self.steps)
}
}

impl PartialOrd for Search {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

fn parse(input: &str) -> Result<Vec<Coords>, AocError> {
let bytes = input
.lines()
.map(|line| {
line.split_once(",")
.and_then(|(x, y)| {
let pos = (x.parse::<usize>().ok()?, y.parse::<usize>().ok()?);

Some(pos)
})
.ok_or_else(|| AocError::parse(input, "Invalid input"))
})
.try_collect()?;

Ok(bytes)
}

// Manhattan distance as the heuristic function
fn heuristic(a: Coords, b: Coords) -> u32 {
(a.0.abs_diff(b.0) + a.1.abs_diff(b.1)) as u32
}

pub fn a_star(corrupted: &[Coords], start: Coords, end: Coords) -> Option<u32> {
let corrupted: HashSet<&Coords> = corrupted.iter().collect();
let width = end.0 as isize + 1;
let height = end.1 as isize + 1;

if corrupted.contains(&start) || corrupted.contains(&end) {
return None;
}

let mut g = HashMap::new();
let mut f = HashMap::new();
let mut open_set = BinaryHeap::new();
let mut closed_set = HashSet::new();

g.insert(start, 0);
f.insert(start, heuristic(start, end));

open_set.push(Search {
steps: f[&start],
position: start,
});

while let Some(Search { position, .. }) = open_set.pop() {
if position == end {
return g.get(&position).cloned();
}

if closed_set.contains(&position) {
continue;
}

closed_set.insert(position);

for (dx, dy) in DIRECTIONS {
let (x, y) = (position.0 as isize + dx, position.1 as isize + dy);
if x < 0 || y < 0 || x >= width || y >= height {
continue;
}

let neighbour = (x as usize, y as usize);

if corrupted.contains(&neighbour) || closed_set.contains(&neighbour) {
continue;
}

let tentative_g = g.entry(position).or_default().saturating_add(1);
let existing_g = g.get(&neighbour).unwrap_or(&u32::MAX);

if tentative_g < *existing_g {
g.insert(neighbour, tentative_g);
f.insert(neighbour, tentative_g + heuristic(neighbour, end));

open_set.push(Search {
steps: tentative_g + heuristic(neighbour, end),
position: neighbour,
});
}
}
}

None
}

pub struct Day18;
impl Solution for Day18 {
type A = u32;
type B = String;

fn default_input(&self) -> &'static str {
include_str!("../../../inputs/2024/day18.txt")
}

fn part_1(&self, input: &str) -> Result<u32, AocError> {
let corrupted = parse(input)?;

let start = (0, 0);
let end = (70, 70);
let min_distance = a_star(&corrupted[0..1024], start, end)
.ok_or_else(|| AocError::logic("No path found"))?;

Ok(min_distance)
}

fn part_2(&self, input: &str) -> Result<String, AocError> {
let corrupted = parse(input)?;

let mut t = 1024;
let start = (0, 0);
let end = (70, 70);

while a_star(&corrupted[0..t], start, end).is_some() {
t += 1;
}

Ok(format!("{},{}", corrupted[t - 1].0, corrupted[t - 1].1))
}
}

#[cfg(test)]
mod tests {
use super::*;

const TEST_INPUT: &str = "5,4\n\
4,2\n\
4,5\n\
3,0\n\
2,1\n\
6,3\n\
2,4\n\
1,5\n\
0,6\n\
3,3\n\
2,6\n\
5,1\n\
1,2\n\
5,5\n\
2,5\n\
6,5\n\
1,4\n\
0,4\n\
6,4\n\
1,1\n\
6,1\n\
1,0\n\
0,5\n\
1,6\n\
2,0";

#[test]
fn it_solves_part1_example() {
let bytes = parse(TEST_INPUT).unwrap();

let min_distance = a_star(&bytes[0..12], (0, 0), (6, 6));

assert_eq!(min_distance, Some(22));
}

#[test]
fn it_solves_part2_example() {
let bytes = parse(TEST_INPUT).unwrap();

let mut t = 12;

while a_star(&bytes[0..t], (0, 0), (6, 6)).is_some() {
t += 1;
}

assert_eq!(t, 21);
}
}
8 changes: 4 additions & 4 deletions aoc-solver/src/y2024/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub mod day14;
pub mod day15;
pub mod day16;
pub mod day17;
// pub mod day18;
pub mod day18;
// pub mod day19;
// pub mod day20;
// pub mod day21;
Expand All @@ -26,7 +26,7 @@ pub mod day17;
// pub mod day24;
// pub mod day25;

pub const MAX_DAYS: u8 = 17;
pub const MAX_DAYS: u8 = 18;

pub struct Y2024;

Expand All @@ -50,7 +50,7 @@ impl Solver for Y2024 {
15 => day15::Day15.run(input, 15, 2024),
16 => day16::Day16.run(input, 16, 2024),
17 => day17::Day17.run(input, 17, 2024),
// 18 => day18::Day18.run(input, 18, 2024),
18 => day18::Day18.run(input, 18, 2024),
// 19 => day19::Day19.run(input, 19, 2024),
// 20 => day20::Day20.run(input, 20, 2024),
// 21 => day21::Day21.run(input, 21, 2024),
Expand Down Expand Up @@ -92,7 +92,7 @@ impl Solver for Y2024 {
15 => include_str!("./day15.rs"),
16 => include_str!("./day16.rs"),
17 => include_str!("./day17.rs"),
// 18 => include_str!("./day18.rs"),
18 => include_str!("./day18.rs"),
// 19 => include_str!("./day19.rs"),
// 20 => include_str!("./day20.rs"),
// 21 => include_str!("./day21.rs"),
Expand Down
1 change: 1 addition & 0 deletions aoc-web/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub fn header(props: &HeaderProps) -> Html {
<NavLink route={Route::WarehouseRobot} current={props.route.clone()} text={"15+"}/>
<NavLink route={Route::Solution { year: 2024, day: 16 }} current={props.route.clone()} text={"16"}/>
<NavLink route={Route::Solution { year: 2024, day: 17 }} current={props.route.clone()} text={"17"}/>
<NavLink route={Route::Solution { year: 2024, day: 18 }} current={props.route.clone()} text={"18"}/>
</>
}
},
Expand Down
Loading

0 comments on commit cbe5199

Please sign in to comment.