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