day 16
This commit is contained in:
parent
51777c2af8
commit
534ebef096
6
day16/Cargo.toml
Normal file
6
day16/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "day16"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
249
day16/src/main.rs
Normal file
249
day16/src/main.rs
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user