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 }