From 9ca870d1bbf6eadb450b4135a4c56273c7d452cd Mon Sep 17 00:00:00 2001 From: sepia Date: Fri, 20 Dec 2024 16:50:19 -0600 Subject: [PATCH] Day 20 (refactor) --- .gitignore | 2 ++ src/day20.rs | 82 +++++++++++++----------------------------------- src/util/maps.rs | 26 ++++++++++++++- 3 files changed, 48 insertions(+), 62 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fedaa2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.env diff --git a/src/day20.rs b/src/day20.rs index c6477db..c05e0a8 100644 --- a/src/day20.rs +++ b/src/day20.rs @@ -1,74 +1,34 @@ use crate::util::maps::*; +use itertools::Itertools; -const CHEAT_DURATION: u64 = 20; +const CHEAT_DURATION: usize = 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 input: World = World::from_string(&crate::input(20)); + let maze: World = input.map(|&c| c == '#'); - let costs = path_costs(&maze, ex, ey); + let (end_x, end_y, _) = input.enumerate().find(|(_, _, &c)| c == 'E').unwrap(); + let distance_map: World> = maze.tile_distances(end_x, end_y); - let free_spaces: Vec<(usize, usize)> = maze + let walkable_tiles: Vec<(usize, usize)> = distance_map .enumerate() - .filter_map(|(x, y, &b)| if !b { Some((x, y)) } else { None }) + .filter_map(|(x, y, &d)| if d.is_some() { Some((x, y)) } else { None }) .collect(); - let all_cheats: Vec = free_spaces + let tile_pairs = walkable_tiles .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(); + .cartesian_product(walkable_tiles.iter()) + .filter(|(&(ax, ay), &(bx, by))| !(ax == bx && ay == by)); + let all_cheats = tile_pairs + .filter(|(&(ax, ay), &(bx, by))| taxicab_distance(ax, ay, bx, by) <= CHEAT_DURATION) + .map(|(&(ax, ay), &(bx, by))| { + let taxi_distance = taxicab_distance(ax, ay, bx, by) as i64; + let a_distance_to_end = distance_map.get(ax, ay).unwrap().unwrap() as i64; + let b_distance_to_end = distance_map.get(bx, by).unwrap().unwrap() as i64; + a_distance_to_end - b_distance_to_end - taxi_distance + }); + let good_cheats = all_cheats.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 + println!("{good_cheats} cheats save 100 or more picoseconds."); } fn taxicab_distance(ax: usize, ay: usize, bx: usize, by: usize) -> usize { diff --git a/src/util/maps.rs b/src/util/maps.rs index e7e36a2..a2eeb15 100644 --- a/src/util/maps.rs +++ b/src/util/maps.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{fmt::Display, intrinsics::atomic_cxchgweak_acquire_acquire}; use itertools::Itertools; @@ -187,3 +187,27 @@ where f.write_str(&final_string) } } + +impl World { + pub fn tile_distances(&self, x: usize, y: usize) -> World> { + let mut costs = World::from_dimensions(self.width(), self.height(), None); + let mut tile_stack: Vec<(usize, usize, u64)> = vec![]; + tile_stack.push((x, y, 0)); + while let Some((x, y, cost_so_far)) = tile_stack.pop() { + let adjacents = self + .adjacent_locations(x, y) + .into_iter() + .filter(|&(nx, ny)| !self.get(nx, ny).unwrap()); + for (nx, ny) in adjacents { + let cost = cost_so_far + 1; + let old_cost = costs.get(nx, ny).unwrap().unwrap_or(u64::MAX); + if cost < old_cost { + costs.set(nx, ny, Some(cost)); + tile_stack.push((nx, ny, cost)); + } + } + } + + costs + } +}