From 79c8ed57447e424493a425c641a89ef6bd1f6087 Mon Sep 17 00:00:00 2001 From: sepia Date: Fri, 20 Dec 2024 15:41:31 -0600 Subject: [PATCH] Day 20 --- src/day20.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 39 ++++++++++++++------------ 2 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 src/day20.rs diff --git a/src/day20.rs b/src/day20.rs new file mode 100644 index 0000000..c6477db --- /dev/null +++ b/src/day20.rs @@ -0,0 +1,78 @@ +use crate::util::maps::*; + +const CHEAT_DURATION: u64 = 20; + +pub fn day20() { + let input = World::from_string(&crate::input(20)); + let maze = input.map(|&c| c == '#'); + let (ex, ey, _) = input.enumerate().find(|(_, _, &c)| c == 'E').unwrap(); + + let costs = path_costs(&maze, ex, ey); + + let free_spaces: Vec<(usize, usize)> = maze + .enumerate() + .filter_map(|(x, y, &b)| if !b { Some((x, y)) } else { None }) + .collect(); + let all_cheats: Vec = free_spaces + .iter() + .flat_map(|&(ax, ay)| { + free_spaces + .iter() + .filter_map(|&(bx, by)| { + if ax == bx && ay == by { + return None; + } + let taxi_cost = taxicab_distance(ax, ay, bx, by) as u64; + if taxi_cost > CHEAT_DURATION { + return None; + } + let a_cost = costs.get(ax, ay).unwrap(); + let b_cost = costs.get(bx, by).unwrap(); + // a cheat's timesave is the cost of a - the cost of b, minus the time it takes to do the cheat + // and we return None for cheats that have negative timesave (why would you cheat to do worse?) + a_cost.checked_sub(b_cost)?.checked_sub(taxi_cost) + }) + .collect::>() + }) + .collect(); + let good_cheats = all_cheats + .iter() + .filter(|&&advantage| advantage >= 100) + .count(); + + println!("{good_cheats}"); +} + +fn path_costs(world: &World, x: usize, y: usize) -> World { + fn calculate_path_costs( + world: &World, + x: usize, + y: usize, + cost_so_far: u64, + costs: &mut World, + ) { + let adjacents = world + .adjacent_locations(x, y) + .into_iter() + .filter(|&(nx, ny)| !world.get(nx, ny).unwrap()); + for (nx, ny) in adjacents { + let cost = cost_so_far + 1; + let old_cost = costs.get(nx, ny).unwrap(); + if cost < old_cost { + costs.set(nx, ny, cost); + calculate_path_costs(world, nx, ny, cost, costs) + } + } + } + + let mut costs = World::from_dimensions(world.width(), world.height(), u64::MAX); + costs.set(x, y, 0); + calculate_path_costs(world, x, y, 0, &mut costs); + costs +} + +fn taxicab_distance(ax: usize, ay: usize, bx: usize, by: usize) -> usize { + let dx = if ax < bx { bx - ax } else { ax - bx }; + let dy = if ay < by { by - ay } else { ay - by }; + dx + dy +} diff --git a/src/main.rs b/src/main.rs index ebf949c..fb4be0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,22 +3,6 @@ pub mod util; #[allow(dead_code)] mod day1; #[allow(dead_code)] -mod day2; -#[allow(dead_code)] -mod day3; -#[allow(dead_code)] -mod day4; -#[allow(dead_code)] -mod day5; -#[allow(dead_code)] -mod day6; -#[allow(dead_code)] -mod day7; -#[allow(dead_code)] -mod day8; -#[allow(dead_code)] -mod day9; -#[allow(dead_code)] mod day10; #[allow(dead_code)] mod day11; @@ -36,10 +20,28 @@ mod day16; mod day17; #[allow(dead_code)] mod day18; +#[allow(dead_code)] mod day19; +#[allow(dead_code)] +mod day2; +mod day20; +#[allow(dead_code)] +mod day3; +#[allow(dead_code)] +mod day4; +#[allow(dead_code)] +mod day5; +#[allow(dead_code)] +mod day6; +#[allow(dead_code)] +mod day7; +#[allow(dead_code)] +mod day8; +#[allow(dead_code)] +mod day9; fn main() { - day19::day19(); + day20::day20(); } pub fn input(day: u8) -> String { @@ -47,7 +49,8 @@ pub fn input(day: u8) -> String { let token = std::env::var("AOC_TOKEN").expect("AOC_TOKEN must be set in .env file"); let client = reqwest::blocking::Client::new(); - client.get(format!("https://adventofcode.com/2024/day/{}/input", day)) + client + .get(format!("https://adventofcode.com/2024/day/{}/input", day)) .header("cookie", format!("session={}", token)) .send() .expect("Failed to get input")