Solve 20
This commit is contained in:
parent
5958039ffb
commit
6252e787ad
1 changed files with 116 additions and 0 deletions
116
src/bin/20.rs
Normal file
116
src/bin/20.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use aoc::*;
|
||||
use glam::IVec2;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::iter;
|
||||
|
||||
const INPUT: &str = include_str!("../../input/20");
|
||||
|
||||
fn main() {
|
||||
println!("Part 1: {}", part1(INPUT));
|
||||
println!("Part 2: {}", part2(INPUT));
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> usize {
|
||||
let maze = Maze::parse(input);
|
||||
count_cheats(&maze, 2)
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> usize {
|
||||
let maze = Maze::parse(input);
|
||||
count_cheats(&maze, 20)
|
||||
}
|
||||
|
||||
fn count_cheats(maze: &Maze, max_cheat: u32) -> usize {
|
||||
let path = maze.path();
|
||||
let mut cheats = BTreeMap::new();
|
||||
for from_i in 0..(path.len() - 1) {
|
||||
for to_i in (from_i + 1)..path.len() {
|
||||
let from = path[from_i];
|
||||
let to = path[to_i];
|
||||
let distance = distance(from, to);
|
||||
if distance > max_cheat {
|
||||
// Too far to cheat
|
||||
continue;
|
||||
}
|
||||
if distance == 1 {
|
||||
// Not really a cheat
|
||||
continue;
|
||||
}
|
||||
let saved = to_i - from_i - distance as usize;
|
||||
if saved == 0 {
|
||||
// Nothing saved
|
||||
continue;
|
||||
}
|
||||
*cheats.entry(saved).or_insert(0usize) += 1;
|
||||
}
|
||||
}
|
||||
//println!("{cheats:#?}");
|
||||
cheats
|
||||
.iter()
|
||||
.filter_map(|(&saved, ×)| if saved >= 100 { Some(times) } else { None })
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn distance(a: IVec2, b: IVec2) -> u32 {
|
||||
a.x.abs_diff(b.x) + a.y.abs_diff(b.y)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Maze {
|
||||
start: IVec2,
|
||||
end: IVec2,
|
||||
tiles: HashSet<IVec2>,
|
||||
}
|
||||
|
||||
impl Maze {
|
||||
fn path(&self) -> Vec<IVec2> {
|
||||
let mut path = vec![self.start];
|
||||
loop {
|
||||
let head = *path.last().unwrap();
|
||||
if head == self.end {
|
||||
return path;
|
||||
}
|
||||
for dir in DIRECTIONS4 {
|
||||
let next = head + dir;
|
||||
if self.tiles.contains(&next) && !path.contains(&next) {
|
||||
path.push(next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> Self {
|
||||
let mut start = IVec2::ZERO;
|
||||
let mut end = IVec2::ZERO;
|
||||
let mut tiles = HashSet::new();
|
||||
let mut walls = HashSet::new();
|
||||
|
||||
let input = input
|
||||
.lines()
|
||||
.enumerate()
|
||||
.flat_map(|(y, line)| line.chars().enumerate().zip(iter::repeat(y)))
|
||||
.map(|((x, c), y)| (IVec2::new(x as i32, y as i32), c));
|
||||
for (pos, c) in input {
|
||||
match c {
|
||||
'S' => {
|
||||
start = pos;
|
||||
tiles.insert(pos);
|
||||
}
|
||||
'E' => {
|
||||
end = pos;
|
||||
tiles.insert(pos);
|
||||
}
|
||||
'#' => {
|
||||
walls.insert(pos);
|
||||
}
|
||||
'.' => {
|
||||
tiles.insert(pos);
|
||||
}
|
||||
other => panic!("unknown tile: '{other}'"),
|
||||
}
|
||||
}
|
||||
|
||||
Self { start, end, tiles }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue