day 18 task 1
This commit is contained in:
217
src/tasks/day18.rs
Normal file
217
src/tasks/day18.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use itertools::Itertools;
|
||||
use std::cmp::{Ord, Ordering, PartialOrd};
|
||||
use std::collections::BinaryHeap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn run() {
|
||||
let input: Map = Map(std::fs::read_to_string("input/day18.txt")
|
||||
.unwrap()
|
||||
.lines()
|
||||
.map(|line| line.chars().collect_vec())
|
||||
.collect_vec());
|
||||
|
||||
task1(input.clone());
|
||||
}
|
||||
|
||||
fn task1(map: Map) {
|
||||
let mut all_keys = map
|
||||
.0
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|c| c.is_alphabetic() && c.is_lowercase())
|
||||
.collect_vec();
|
||||
all_keys.sort();
|
||||
let all_keys: String = all_keys.into_iter().collect();
|
||||
let map = Rc::from(map);
|
||||
|
||||
let mut visited: HashSet<StateSummary> = HashSet::new();
|
||||
let mut open: BinaryHeap<State> = BinaryHeap::new();
|
||||
let mut best_found_for_all = std::usize::MAX;
|
||||
|
||||
open.push(State::new(map.clone()));
|
||||
while let Some(state) = open.pop() {
|
||||
let summary = StateSummary::from(&state);
|
||||
if visited.contains(&summary) || best_found_for_all <= state.steps_taken {
|
||||
// there could come no better solution
|
||||
continue;
|
||||
}
|
||||
|
||||
if summary.1 == all_keys {
|
||||
if state.steps_taken < best_found_for_all {
|
||||
best_found_for_all = state.steps_taken;
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.get_available_options()
|
||||
.into_iter()
|
||||
.for_each(|s| open.push(s));
|
||||
|
||||
visited.insert(summary);
|
||||
}
|
||||
println!(
|
||||
"Task 1: best bound to get all keys is {}",
|
||||
best_found_for_all
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
||||
struct StateSummary(Pos, String);
|
||||
impl StateSummary {
|
||||
fn from(state: &State) -> Self {
|
||||
let mut s = state.opened.iter().collect_vec();
|
||||
s.sort();
|
||||
Self(
|
||||
state.current,
|
||||
s.into_iter()
|
||||
.map(|c| {
|
||||
let mut x = *c;
|
||||
x.make_ascii_lowercase();
|
||||
x
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct StateSummaryDist(Pos, HashSet<char>, usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
struct Map(Vec<Vec<char>>);
|
||||
|
||||
impl Map {
|
||||
fn coordinate_of(&self, symbol: char) -> Pos {
|
||||
self.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(y, line)| {
|
||||
line.iter()
|
||||
.enumerate()
|
||||
.find(|(_x, c)| **c == symbol)
|
||||
.map(|(x, _c)| Pos(x, y))
|
||||
})
|
||||
.find(|op| op.is_some())
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn reachable_keys(&self, start: Pos, open_doors: &HashSet<char>) -> Vec<(char, usize, Pos)> {
|
||||
let all_keys = 'a'..='z';
|
||||
let mut result = vec![];
|
||||
|
||||
let mut open: VecDeque<(Pos, usize)> = VecDeque::new();
|
||||
open.push_back((start, 0));
|
||||
let mut visited: HashSet<Pos> = HashSet::new();
|
||||
while let Some((current, walked)) = open.pop_front() {
|
||||
if visited.contains(¤t) {
|
||||
continue;
|
||||
}
|
||||
let field = self.0[current.1][current.0];
|
||||
let field_is_key = all_keys.contains(&field);
|
||||
// if can move over current type: push neighbors to open
|
||||
if field == '.' || field == '@' || open_doors.contains(&field) || field_is_key {
|
||||
current
|
||||
.neighbors()
|
||||
.iter()
|
||||
.for_each(|n| open.push_back((*n, walked + 1)));
|
||||
}
|
||||
|
||||
// if it is a key: push it to result
|
||||
if field_is_key {
|
||||
result.push((field, walked, current));
|
||||
}
|
||||
|
||||
// anyways: push to visited
|
||||
visited.insert(current);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
struct Pos(usize, usize);
|
||||
|
||||
impl Pos {
|
||||
fn neighbors(&self) -> [Pos; 4] {
|
||||
[
|
||||
Pos(self.0 - 1, self.1),
|
||||
Pos(self.0 + 1, self.1),
|
||||
Pos(self.0, self.1 - 1),
|
||||
Pos(self.0, self.1 + 1),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct State {
|
||||
current: Pos,
|
||||
opened: HashSet<char>, // capital letter
|
||||
map: Rc<Map>,
|
||||
steps_taken: usize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(map: Rc<Map>) -> Self {
|
||||
Self {
|
||||
current: map.coordinate_of('@'),
|
||||
opened: HashSet::new(),
|
||||
map: map,
|
||||
steps_taken: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_available_options(&self) -> Vec<State> {
|
||||
// find all keys that are not yet collected + their distance + position
|
||||
let next_keys = self
|
||||
.map
|
||||
.reachable_keys(self.current, &self.opened)
|
||||
.into_iter()
|
||||
.filter(|(key, _, _)| {
|
||||
let mut c = *key;
|
||||
c.make_ascii_uppercase();
|
||||
!self.opened.contains(&c)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// create new state with one open door added + steps increased + current position updated
|
||||
next_keys
|
||||
.into_iter()
|
||||
.map(|(key, distance, current)| self.advance(key, distance, current))
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
fn advance(&self, key_added: char, additional_steps: usize, new_pos: Pos) -> Self {
|
||||
let mut open_doors = self.opened.clone();
|
||||
let mut door = key_added;
|
||||
door.make_ascii_uppercase();
|
||||
open_doors.insert(door);
|
||||
Self {
|
||||
current: new_pos,
|
||||
opened: open_doors,
|
||||
map: self.map.clone(),
|
||||
steps_taken: self.steps_taken + additional_steps,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The priority queue depends on `Ord`.
|
||||
// Explicitly implement the trait so the queue becomes a min-heap
|
||||
// instead of a max-heap.
|
||||
impl Ord for State {
|
||||
fn cmp(&self, other: &State) -> Ordering {
|
||||
// Notice that the we flip the ordering on costs.
|
||||
// In case of a tie we compare positions - this step is necessary
|
||||
// to make implementations of `PartialEq` and `Ord` consistent.
|
||||
other.steps_taken.cmp(&self.steps_taken)
|
||||
}
|
||||
}
|
||||
|
||||
// `PartialOrd` needs to be implemented as well.
|
||||
impl PartialOrd for State {
|
||||
fn partial_cmp(&self, other: &State) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user