diff --git a/src/tasks/day18.rs b/src/tasks/day18.rs index bc36a15..016c0b0 100644 --- a/src/tasks/day18.rs +++ b/src/tasks/day18.rs @@ -5,6 +5,7 @@ use std::collections::HashSet; use std::collections::VecDeque; use std::rc::Rc; +#[allow(dead_code)] pub fn run() { let input: Map = Map(std::fs::read_to_string("input/day18.txt") .unwrap() @@ -12,10 +13,13 @@ pub fn run() { .map(|line| line.chars().collect_vec()) .collect_vec()); - task1(input.clone()); + let t1 = task(input.clone()); + println!("Task 1: best bound to get all keys is {}", t1); + let t2 = task(input.clone().split_robot()); + println!("Task 2: best bound to get all keys is {}", t2); } -fn task1(map: Map) { +fn task(map: Map) -> usize { let mut all_keys = map .0 .iter() @@ -51,20 +55,18 @@ fn task1(map: Map) { visited.insert(summary); } - println!( - "Task 1: best bound to get all keys is {}", - best_found_for_all - ); + + best_found_for_all } #[derive(Eq, PartialEq, Hash, Debug)] -struct StateSummary(Pos, String); +struct StateSummary(Vec, String); impl StateSummary { fn from(state: &State) -> Self { let mut s = state.opened.iter().collect_vec(); s.sort(); Self( - state.current, + state.currents.clone(), s.into_iter() .map(|c| { let mut x = *c; @@ -82,52 +84,96 @@ struct StateSummaryDist(Pos, HashSet, usize); struct Map(Vec>); impl Map { - fn coordinate_of(&self, symbol: char) -> Pos { + fn coordinate_of(&self, symbol: char) -> Vec { self.0 .iter() .enumerate() - .map(|(y, line)| { + .fold(Vec::new(), |vec, (y, line)| { line.iter() .enumerate() - .find(|(_x, c)| **c == symbol) - .map(|(x, _c)| Pos(x, y)) + .filter(|(_x, c)| **c == symbol) + .fold(vec, |mut vec, (x, _c)| { + vec.push(Pos(x, y)); + vec + }) }) - .find(|op| op.is_some()) - .unwrap() - .unwrap() } - fn reachable_keys(&self, start: Pos, open_doors: &HashSet) -> Vec<(char, usize, Pos)> { + /** + * return: (char: found key, usize1: index of moved robot, + * usize2: distance that robot moved, Pos: new position of robot[index]), + * usize3: number of empty points on path to last_door_opened + */ + fn reachable_keys( + &self, + start_points: Vec, + open_doors: &HashSet, + last_door_opened: Option, + ) -> Vec<(char, usize, usize, Pos, usize)> { 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 = 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))); + let bfs = |start: Pos, open_doors: &HashSet| { + // key, distance, new_pos, number of steps of distance until last_open door is met + let mut result: Vec<(char, usize, Pos, usize)> = vec![]; + let mut open: VecDeque<(Pos, usize, usize)> = VecDeque::new(); //position, distance form start, distance until last door opened is met + open.push_back((start, 0, 0)); + let mut visited: HashSet = HashSet::new(); + while let Some((current, walked, ldod)) = 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 { + let mut ldod = 0; + if let Some(ldo) = last_door_opened { + if ldo == field { + ldod = walked; + } + }; + current + .neighbors() + .iter() + .for_each(|n| open.push_back((*n, walked + 1, ldod))); + } + + // if it is a key: push it to result + if field_is_key { + result.push((field, walked, current, ldod)); + } + + // anyways: push to visited + visited.insert(current); } - // if it is a key: push it to result - if field_is_key { - result.push((field, walked, current)); - } + result + }; - // anyways: push to visited - visited.insert(current); - } + start_points + .iter() + .enumerate() + .flat_map(|(robot_i, robot_pos)| { + bfs(*robot_pos, open_doors) + .into_iter() + .map(|(key, dist, final_pos, ldod)| (key, robot_i, dist, final_pos, ldod)) + .collect_vec() + }) + .collect_vec() + } - result + fn split_robot(mut self) -> Self { + let Pos(x, y) = self.coordinate_of('@')[0]; + self.0[x][y] = '#'; + self.0[x - 1][y - 1] = '@'; + self.0[x - 1][y] = '#'; + self.0[x - 1][y + 1] = '@'; + self.0[x + 1][y] = '#'; + self.0[x + 1][y + 1] = '@'; + self.0[x][y - 1] = '#'; + self.0[x + 1][y - 1] = '@'; + self.0[x][y + 1] = '#'; + self } } @@ -147,19 +193,25 @@ impl Pos { #[derive(Eq, PartialEq)] struct State { - current: Pos, + currents: Vec, + underrun: Vec, // the number of steps a robot is behind the leading robots steps opened: HashSet, // capital letter map: Rc, steps_taken: usize, + last_door_opened: Option, } impl State { fn new(map: Rc) -> Self { + let currents = map.coordinate_of('@'); + let no_robots = currents.len(); Self { - current: map.coordinate_of('@'), + currents: currents, + underrun: std::iter::repeat(0).take(no_robots).collect_vec(), opened: HashSet::new(), map: map, steps_taken: 0, + last_door_opened: None, } } @@ -167,9 +219,9 @@ impl State { // find all keys that are not yet collected + their distance + position let next_keys = self .map - .reachable_keys(self.current, &self.opened) + .reachable_keys(self.currents.clone(), &self.opened, self.last_door_opened) .into_iter() - .filter(|(key, _, _)| { + .filter(|(key, _, _, _, _)| { let mut c = *key; c.make_ascii_uppercase(); !self.opened.contains(&c) @@ -179,20 +231,52 @@ impl State { // 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)) + .map(|(key, robot_i, distance, current, ldod)| { + self.advance(key, robot_i, distance, current, ldod) + }) .collect_vec() } - fn advance(&self, key_added: char, additional_steps: usize, new_pos: Pos) -> Self { + fn advance( + &self, + key_added: char, + robot_index: usize, + additional_steps: usize, + new_pos: Pos, + ldod: usize, + ) -> Self { let mut open_doors = self.opened.clone(); let mut door = key_added; door.make_ascii_uppercase(); open_doors.insert(door); + let mut positions = self.currents.clone(); + positions[robot_index] = new_pos; + let underrun = self + .underrun + .iter() + .enumerate() + .map(|(i, u)| { + if i != robot_index { + *u + additional_steps + } else { + 0 + } + }) + .collect(); + + let usable_underrun = if self.underrun[robot_index] >= ldod { + ldod + } else { + self.underrun[robot_index] + }; + let steps_diff = additional_steps - usable_underrun; Self { - current: new_pos, + currents: positions, + underrun: underrun, opened: open_doors, map: self.map.clone(), - steps_taken: self.steps_taken + additional_steps, + steps_taken: self.steps_taken + steps_diff as usize, + last_door_opened: Some(key_added), } } }