This commit is contained in:
Shoofle 2024-12-16 12:58:14 -05:00
parent 51777c2af8
commit 534ebef096
2 changed files with 255 additions and 0 deletions

6
day16/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "day16"
version = "0.1.0"
edition = "2021"
[dependencies]

249
day16/src/main.rs Normal file
View File

@ -0,0 +1,249 @@
use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::BinaryHeap;
use std::env;
use std::fs;
use crate::Dir::{North, South, East, West };
type Coord = (i32, i32);
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
enum Dir { North, South, East, West }
#[derive(Copy, Clone, Eq, PartialEq)]
struct State {
cost: i32,
position: Coord,
facing: Dir,
}
impl Ord for State {
fn cmp(&self, other: &Self) -> Ordering {
// flipped ordering on cost, so that it's a min-heap and not a max-heap
other.cost.cmp(&self.cost)
.then_with(|| self.position.cmp(&other.position))
.then_with(|| self.facing.cmp(&other.facing))
}
}
// `PartialOrd` needs to be implemented as well.
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn step(start: &Coord, d: Dir, steps: i32) -> Coord {
match d {
North => (start.0, start.1 - steps),
South => (start.0, start.1 + steps),
East => (start.0 + steps, start.1),
West => (start.0 - steps, start.1),
}
}
fn main() {
println!("Hello, AoC day 13!");
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
println!("wrong number of arguments!");
std::process::exit(1);
}
let file_path = &args[1];
let file = fs::read_to_string(file_path)
.expect("should be able to read the file");
let mut map = HashMap::new();
let mut start = (0,0);
let mut exit = (0,0);
let mut y = 0;
for line in file.lines() {
let mut x = 0;
for c in line.chars() {
match c {
'S' => { start = (x,y); map.insert((x,y), '.'); },
'E' => { exit = (x,y); map.insert((x,y), '.'); },
_ => { map.insert((x,y), c); }
};
x += 1;
}
y += 1;
}
let score = solve_maze(&map, start, exit);
println!("the score was {score:?}");
let distances = assign_distances(&map, start);
println!("found {} distances", distances.len());
let paths = find_paths(&map, &distances, start, exit);
let mut touched_squares: HashSet<Coord> = HashSet::new();
for path in paths {
for (location, _facing) in path {
touched_squares.insert(location);
}
}
println!("there were {} squares on the best paths", touched_squares.len());
}
fn solve_maze(map: &HashMap<Coord, char>, start: Coord, exit: Coord) -> Option<i32> {
let mut heap = BinaryHeap::new();
let mut distances: HashMap<(Coord, Dir), i32> = HashMap::new();
heap.push(State { position: start, facing: East, cost: 0});
while let Some(State { position, facing, cost}) = heap.pop() {
if position == exit { return Some(cost); }
if let Some(previous_cost) = distances.get(&(position, facing)) {
if *previous_cost < cost { continue; }
}
distances.insert((position, facing), cost);
for new_dir in [North, South, East, West] {
let new_spot = step(&position, new_dir, 1);
let mut new_cost = cost + 1;
if facing != new_dir { new_cost = new_cost + 1000}
if let Some(d) = distances.get(&(new_spot, new_dir)) {
if *d < new_cost { continue; }
}
if map.get(&new_spot) == Some(&'.') {
heap.push(State {
position: new_spot,
facing: new_dir,
cost: new_cost
});
}
}
}
return None;
}
fn assign_distances(map: &HashMap<Coord, char>, start: Coord) -> HashMap<(Coord, Dir), i32> {
let mut heap = BinaryHeap::new();
let mut distances = HashMap::new();
heap.push(State { position: start, facing: East, cost: 0});
while let Some(State { position, facing, cost}) = heap.pop() {
if let Some(previous_cost) = distances.get(&(position, facing)) {
if *previous_cost < cost { continue; }
}
distances.insert((position, facing), cost);
// option to rotate in place! very expensive
for new_dir in [North, South, East, West] {
let new_cost = cost + 1000;
if let Some(d) = distances.get(&(position, new_dir)) {
if *d < new_cost { continue; }
}
heap.push(State {
position: position,
facing: new_dir,
cost: new_cost
});
}
let forward_position = step(&position, facing, 1);
if Some(&'.') == map.get(&forward_position) {
let new_cost = cost + 1;
if let Some(d) = distances.get(&(forward_position, facing)) {
if *d < new_cost { continue; }
}
heap.push(State {
position: forward_position,
facing: facing,
cost: new_cost,
})
}
}
return distances;
}
fn find_paths(
map: &HashMap<Coord, char>,
distances: &HashMap<(Coord, Dir), i32>,
start: Coord,
end: Coord
) -> Vec<Vec<((i32, i32), Dir)>> {
let mut paths: Vec<Vec<(Coord, Dir)>> = Vec::new();
let mut output: Vec<Vec<(Coord, Dir)>> = Vec::new();
let mut shortest_end_distance = distances.get(&(end, North)).expect("the end has to be reachable");
for dir in [North, South, East, West] {
if let Some(d) = distances.get(&(end, dir)) {
if d < shortest_end_distance {
shortest_end_distance = d;
}
}
}
for dir in [North, South, East, West] {
if let Some(d) = distances.get(&(end, dir)) {
if d == shortest_end_distance {
paths.push(vec![(end, dir)]);
}
}
}
while let Some(mut path) = paths.pop() {
// iterate through paths that lead to the end
// find all neighbors that can step to the current path head
// if the neighbor can step to the current path lead,
if let Some(head) = path.pop() {
if head == (start, East) {
path.push(head);
output.push(path);
continue;
}
let potential_predecessors = state_predecessors(map, head);
let our_distance = distances.get(&head).unwrap();
for state in potential_predecessors {
let candidate_distance = distances.get(&state).unwrap();
if candidate_distance < our_distance {
let mut this_path = path.clone();
this_path.push(head);
this_path.push(state);
paths.push(this_path);
}
}
}
}
return output;
}
fn state_predecessors(map: &HashMap<Coord, char>, end: (Coord, Dir)) -> Vec<(Coord, Dir)> {
let mut output = Vec::new();
let walk = step(&end.0, end.1, -1); // include the possibility that we walked here.
if map.get(&walk) == Some(&'.') { output.push((walk, end.1)); }
for old_dir in [North, South, East, West] {
if old_dir == end.1 { continue; }
output.push((end.0, old_dir));
}
return output;
}