From 4414ced947994065db36ee5c93346de387e1f2a6 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Thu, 23 Dec 2021 16:17:07 +0200 Subject: Day 23 --- inputs/day_23.txt | 5 ++ src/bin/day_23.rs | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 inputs/day_23.txt create mode 100644 src/bin/day_23.rs diff --git a/inputs/day_23.txt b/inputs/day_23.txt new file mode 100644 index 0000000..3d20443 --- /dev/null +++ b/inputs/day_23.txt @@ -0,0 +1,5 @@ +############# +#...........# +###B#B#D#D### + #C#A#A#C# + ######### diff --git a/src/bin/day_23.rs b/src/bin/day_23.rs new file mode 100644 index 0000000..ed2fb01 --- /dev/null +++ b/src/bin/day_23.rs @@ -0,0 +1,224 @@ +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{char as nom_char, line_ending, not_line_ending}, + combinator::value, + multi::{many1, separated_list1}, + sequence::{delimited, pair}, + IResult, +}; +use std::{cmp, collections::HashSet, fs}; + +fn main() -> Result<(), Box> { + let input = fs::read_to_string("inputs/day_23.txt")?; + let maze = parse_maze(&input).unwrap().1; + dbg!(find_shortest_path(&maze).cost); + Ok(()) +} + +fn find_shortest_path(maze: &Maze) -> Move { + let mut visited = HashSet::new(); + visited.insert(maze.clone()); + let mut frontier = vec![Move { + next_state: maze.clone(), + cost: 0, + }]; + + let mut best_so_far: Option = None; + + while let Some(current) = frontier.pop() { + if let Some(best_so_far) = &best_so_far { + if current.cost >= best_so_far.cost { + return best_so_far.clone(); + } + } + + let next_moves: Vec = current + .next_state + .valid_moves() + .into_iter() + .map(|next| Move { + cost: next.cost + current.cost, + ..next + }) + .collect(); + for next in next_moves { + if next.next_state.is_complete() { + best_so_far = if let Some(best_so_far) = best_so_far { + if best_so_far.cost < next.cost { + Some(best_so_far) + } else { + Some(next.clone()) + } + } else { + Some(next.clone()) + }; + } else if !visited.contains(&next.next_state) { + visited.insert(next.next_state.clone()); + frontier.push(next); + } + } + frontier.sort_unstable_by(|a, b| b.cost.cmp(&a.cost)); + } + best_so_far.expect("There is no path through!") +} + +#[derive(Debug, Clone)] +struct Move { + next_state: Maze, + cost: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct Maze { + corridor: Vec>, + rooms: Vec, +} + +impl Maze { + fn is_complete(&self) -> bool { + self.rooms.iter().all(|room| room.is_complete()) + } + + fn valid_moves(&self) -> Vec { + let mut valid_moves = Vec::new(); + + for i in 0..self.corridor.len() { + if let Some(shrimp) = self.corridor[i] { + let target_room = &self.rooms[shrimp / 2 - 1]; + if target_room.can_enter() { + let route_free = (cmp::min(shrimp, i)..=cmp::max(shrimp, i)) + .all(|route_i| route_i == i || self.corridor[route_i].is_none()); + if route_free { + let mut next_state = self.clone(); + next_state.corridor[i] = None; + let next_room = &mut next_state.rooms[shrimp / 2 - 1]; + let room_depth = next_room + .max_free() + .expect("no space in room, but we checked!"); + next_room.contents[room_depth] = Some(shrimp); + let distance = room_depth + 1 + cmp::max(shrimp, i) - cmp::min(shrimp, i); + let cost = calculate_cost(shrimp, distance); + valid_moves.push(Move { next_state, cost }); + } + } + } + } + + for (room_i, room) in self + .rooms + .iter() + .enumerate() + .filter(|(_, room)| !room.can_enter()) + { + if let Some((room_depth, shrimp)) = room + .contents + .iter() + .enumerate() + .filter_map(|(room_depth, maybe_shrimp)| { + maybe_shrimp.map(|shrimp| (room_depth, shrimp)) + }) + .next() + { + for corridor_i in 0..self.corridor.len() { + let in_entrance = self.rooms.iter().any(|room| room.entrance == corridor_i); + let route_free = (cmp::min(room.entrance, corridor_i) + ..=cmp::max(room.entrance, corridor_i)) + .all(|route_i| self.corridor[route_i].is_none()); + if !in_entrance && route_free { + let mut next_state = self.clone(); + next_state.corridor[corridor_i] = Some(shrimp); + next_state.rooms[room_i].contents[room_depth] = None; + + let distance = room_depth + 1 + cmp::max(room.entrance, corridor_i) + - cmp::min(room.entrance, corridor_i); + let cost = calculate_cost(shrimp, distance); + valid_moves.push(Move { next_state, cost }); + } + } + } + } + + valid_moves + } +} + +fn calculate_cost(shrimp: usize, distance: usize) -> usize { + let shrimp_cost = match shrimp { + 2 => 1, + 4 => 10, + 6 => 100, + 8 => 1000, + _ => panic!("Unknown shrimp"), + }; + shrimp_cost * distance +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct Room { + entrance: usize, + contents: Vec>, +} + +impl Room { + fn is_complete(&self) -> bool { + self.contents + .iter() + .all(|slot| slot == &Some(self.entrance)) + } + fn can_enter(&self) -> bool { + self.contents + .iter() + .all(|slot| slot.is_none() || slot == &Some(self.entrance)) + } + fn max_free(&self) -> Option { + self.contents.iter().rposition(|slot| slot.is_none()) + } +} + +fn parse_maze(input: &str) -> IResult<&str, Maze> { + let (input, _) = pair(not_line_ending, line_ending)(input)?; // skip first line + let (input, corridor) = delimited(nom_char('#'), many1(corridor_contents), tag("#\n"))(input)?; + + let (input, rooms_line_1) = delimited( + tag("###"), + separated_list1(nom_char('#'), corridor_contents), + tag("###\n"), + )(input)?; + let (input, rooms_line_2) = delimited( + tag(" #"), + separated_list1(nom_char('#'), corridor_contents), + tag("#\n"), + )(input)?; + + let rooms = vec![ + Room { + entrance: 2, + contents: vec![rooms_line_1[0], Some(8), Some(8), rooms_line_2[0]], + }, + Room { + entrance: 4, + contents: vec![rooms_line_1[1], Some(6), Some(4), rooms_line_2[1]], + }, + Room { + entrance: 6, + contents: vec![rooms_line_1[2], Some(4), Some(2), rooms_line_2[2]], + }, + Room { + entrance: 8, + contents: vec![rooms_line_1[3], Some(2), Some(6), rooms_line_2[3]], + }, + ]; + + Ok((input, Maze { corridor, rooms })) +} + +fn corridor_contents(input: &str) -> IResult<&str, Option> { + alt(( + value(None, nom_char('.')), + value(Some(2), nom_char('A')), + value(Some(4), nom_char('B')), + value(Some(6), nom_char('C')), + value(Some(8), nom_char('D')), + ))(input) +} -- cgit v1.2.3