Skip to content

Commit

Permalink
2024-20
Browse files Browse the repository at this point in the history
  • Loading branch information
fornwall committed Dec 20, 2024
1 parent fa0bc93 commit 47e4a00
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 9 deletions.
128 changes: 119 additions & 9 deletions crates/core/src/year2024/day20.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,128 @@
use crate::input::Input;
use crate::{
common::array_deque::ArrayDeque,
input::{on_error, Input},
};

pub const fn solve(_input: &Input) -> Result<u32, String> {
Ok(0)
const MAX_GRID_SIZE: usize = 150;
const WORK_QUEUE_MAX_SIZE: usize = 1024;

pub fn solve(input: &Input) -> Result<u32, String> {
let width = input
.text
.lines()
.next()
.map(|line| line.len())
.ok_or_else(on_error)? as i16;
let grid = Grid {
s: input.text.as_bytes(),
width,
};
if grid.s.len() != ((grid.width + 1) * grid.width - 1) as usize {
return Err("Invalid input - not a rectangle".to_string());
} else if grid.width >= MAX_GRID_SIZE as i16 {
return Err("Invalid input - too big rectangle".to_string());
}

let mut start_location = (0, 0);
let mut end_location = (0, 0);
for y in 0..grid.width {
for x in 0..grid.width {
match grid.at((x, y)) {
b'S' => start_location = (x, y),
b'E' => end_location = (x, y),
_ => {}
}
}
}
if start_location == (0, 0) {
return Err("No start location".to_string());
} else if end_location == (0, 0) {
return Err("No end location".to_string());
}

let mut to_visit = ArrayDeque::<{ WORK_QUEUE_MAX_SIZE }, (u16, (i16, i16))>::new();
let mut costs = [[u16::MAX; MAX_GRID_SIZE]; MAX_GRID_SIZE];

to_visit.push_back((0, start_location))?;

while let Some((cost, position)) = to_visit.pop_front() {
if costs[position.1 as usize][position.0 as usize] <= cost {
continue;
}
costs[position.1 as usize][position.0 as usize] = cost;
for (dx, dy) in [(0, -1), (1, 0), (0, 1), (-1, 0)] {
let next = (position.0 + dx, position.1 + dy);
if grid.at(next) != b'#' {
to_visit.push_back((cost + 1, next))?;
}
}
}

let diamond_size: i16 = input.part_values(2, 20);

// Second pass to find cheats
let mut visited = [[false; MAX_GRID_SIZE]; MAX_GRID_SIZE];
let mut to_visit = ArrayDeque::<{ WORK_QUEUE_MAX_SIZE }, (i16, i16)>::new();
to_visit.push_back(start_location)?;
let mut num_great_cheats = 0;

while let Some(position) = to_visit.pop_front() {
if visited[position.1 as usize][position.0 as usize] {
continue;
}
let cost = costs[position.1 as usize][position.0 as usize];
visited[position.1 as usize][position.0 as usize] = true;
for (dx, dy) in [(0, -1), (1, 0), (0, 1), (-1, 0)] {
let next = (position.0 + dx, position.1 + dy);
if grid.at(next) != b'#' {
to_visit.push_back(next)?;
}
}

// Cheat:
for dx in -diamond_size..=diamond_size {
for dy in -diamond_size..=diamond_size {
let manhattan_distance = dx.abs() + dy.abs();
if (dx, dy) == (0, 0) || (manhattan_distance > diamond_size) {
continue;
}
let cheat = (position.0 + dx, position.1 + dy);
if grid.at(cheat) != b'#' && costs[cheat.1 as usize][cheat.0 as usize] != u16::MAX {
if let Some(gain) = costs[cheat.1 as usize][cheat.0 as usize]
.checked_sub(cost + manhattan_distance as u16)
{
if gain >= 100 {
num_great_cheats += 1;
}
}
}
}
}
}

Ok(num_great_cheats)
}

struct Grid<'a> {
s: &'a [u8],
width: i16,
}

impl Grid<'_> {
const fn at(&self, position: (i16, i16)) -> u8 {
if position.0 < 0 || position.0 >= self.width || position.1 < 0 || position.1 >= self.width
{
return b'#';
}
self.s[(position.0 + (self.width + 1) * position.1) as usize]
}
}

#[test]
pub fn tests() {
use crate::input::{test_part_one_no_allocations, test_part_two_no_allocations};

let test_input = "";
test_part_one_no_allocations!(test_input => 0);
test_part_two_no_allocations!(test_input => 0);

let real_input = include_str!("day20_input.txt");
test_part_one_no_allocations!(real_input => 0);
test_part_two_no_allocations!(real_input => 0);
test_part_one_no_allocations!(real_input => 1338);
test_part_two_no_allocations!(real_input => 975_376);
}
Loading

0 comments on commit 47e4a00

Please sign in to comment.