Advertisement
nairby

Day 23 Solution

Jan 8th, 2022 (edited)
1,077
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Rust 12.65 KB | None | 0 0
  1. use std::env;
  2. use std::io::{prelude::*, BufReader};
  3. use std::fs::File;
  4. use std::collections::VecDeque;
  5. use std::collections::HashMap;
  6. use std::fmt;
  7.  
  8. use std::collections::BinaryHeap;
  9. use std::cmp::Ordering;
  10.  
  11. #[macro_use] extern crate cached;
  12.  
  13. const HALLWAY_ROW: i64 = 1;
  14. // Column number of each amphipod's room
  15. const ROOM_COLUMN_AMBER:  i64 = 3;
  16. const ROOM_COLUMN_BRONZE: i64 = 5;
  17. const ROOM_COLUMN_COPPER: i64 = 7;
  18. const ROOM_COLUMN_DESERT: i64 = 9;
  19.  
  20. fn amphipod_room_column(species: &AmphipodSpecies) -> i64 {
  21.     match species {
  22.         AmphipodSpecies::Amber  => ROOM_COLUMN_AMBER,
  23.         AmphipodSpecies::Bronze => ROOM_COLUMN_BRONZE,
  24.         AmphipodSpecies::Copper => ROOM_COLUMN_COPPER,
  25.         AmphipodSpecies::Desert => ROOM_COLUMN_DESERT,
  26.     }
  27. }
  28.  
  29. type AmphipodMap = HashMap<(i64,i64),MapType>;
  30.  
  31. #[macro_use] extern crate lazy_static;
  32. lazy_static! {
  33.     static ref AMPHI_MAP: AmphipodMap = {
  34.         let args: Vec<String> = env::args().collect();
  35.         let filename = &args[1];
  36.         let file = File::open(filename).expect("Input file not found.");
  37.         let reader = BufReader::new(file);
  38.         // Process input file char by char
  39.         let mut amphimap: AmphipodMap = AmphipodMap::new();
  40.         for (y,line) in reader.lines().enumerate() {
  41.             for (x,c) in line.unwrap().chars().enumerate() {
  42.                 let (x,y) = (x as i64, y as i64);
  43.                 amphimap.insert((x,y),MapType::from_char_col(c,x));
  44.             }
  45.         }
  46.         amphimap
  47.     };
  48. }
  49.  
  50. enum MapType {
  51.     OutsideMap,
  52.     Wall,
  53.     Hallway,
  54.     Room,
  55.     Entryway,
  56. }
  57. impl MapType {
  58.     pub fn from_char_col(c: char, col: i64) -> Self {
  59.         match (c, col) {
  60.             (' ',_)       => Self::OutsideMap,
  61.             ('#',_)       => Self::Wall,
  62.             ('A'..='D',_) => Self::Room,
  63.             ('.',col)     =>
  64.                 match col {
  65.                     ROOM_COLUMN_AMBER  |
  66.                     ROOM_COLUMN_BRONZE |
  67.                     ROOM_COLUMN_COPPER |
  68.                     ROOM_COLUMN_DESERT => Self::Entryway,
  69.                     _ => Self::Hallway,
  70.                 },
  71.             (other,_) => panic!("Unknown map character: {}", other),
  72.         }
  73.     }
  74. }
  75. impl fmt::Display for MapType {
  76.     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  77.        match self {
  78.            Self::OutsideMap => write!(f, " "),
  79.            Self::Wall       => write!(f, "#"),
  80.            Self::Hallway    => write!(f, "."),
  81.            Self::Room       => write!(f, "_"),
  82.            Self::Entryway   => write!(f, "*"),
  83.        }
  84.    }
  85. }
  86.  
  87. #[derive(Clone, Eq, PartialEq)]
  88. enum AmphipodSpecies {
  89.    Amber,
  90.    Bronze,
  91.    Copper,
  92.    Desert,
  93. }
  94. impl AmphipodSpecies {
  95.    pub fn from_char(c: char) -> Self {
  96.        match c {
  97.            'A' => Self::Amber ,
  98.            'B' => Self::Bronze,
  99.            'C' => Self::Copper,
  100.            'D' => Self::Desert,
  101.            _ => unreachable!(),
  102.        }
  103.    }
  104. }
  105.  
  106. #[derive(Clone)]
  107. struct Amphipod {
  108.    species: AmphipodSpecies,
  109.    x: i64,
  110.    y: i64,
  111. }
  112. impl Amphipod {
  113.    pub fn new(x: i64, y: i64, c: char) -> Self {
  114.        match c {
  115.            'A' | 'B' | 'C' | 'D' => Self { x: x, y: y, species: AmphipodSpecies::from_char(c)},
  116.            _ => unreachable!(),
  117.        }
  118.    }
  119.    pub fn energy_cost(&self) -> usize {
  120.        match self.species {
  121.            AmphipodSpecies::Amber  =>    1,
  122.            AmphipodSpecies::Bronze =>   10,
  123.            AmphipodSpecies::Copper =>  100,
  124.            AmphipodSpecies::Desert => 1000,
  125.        }
  126.    }
  127. }
  128. impl fmt::Display for Amphipod {
  129.    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  130.         match self.species {
  131.             AmphipodSpecies::Amber  => write!(f, "A"),
  132.             AmphipodSpecies::Bronze => write!(f, "B"),
  133.             AmphipodSpecies::Copper => write!(f, "C"),
  134.             AmphipodSpecies::Desert => write!(f, "D"),
  135.         }
  136.     }
  137. }
  138.  
  139. struct State {
  140.     amphipods: Vec<Amphipod>,
  141.     energy: usize,
  142. }
  143. impl Ord for State {
  144.     fn cmp(&self, other: &Self) -> Ordering {
  145.         other.energy.cmp(&self.energy)
  146.     }
  147. }
  148. impl PartialOrd for State {
  149.     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  150.         Some(other.energy.cmp(&self.energy))
  151.     }
  152. }
  153. impl Eq for State {}
  154. impl PartialEq for State {
  155.     fn eq(&self, other: &Self) -> bool {
  156.         other.energy == self.energy
  157.     }
  158. }
  159.  
  160. fn move_from_to(x0: i64, y0: i64, x1: i64, y1: i64, amphipods: &mut Vec<Amphipod>) {
  161.     amphipods.iter_mut().filter(|a| (a.x,a.y) == (x0,y0)).for_each(|mut a| {a.x = x1; a.y = y1;})
  162. }
  163.  
  164. // Get valid destinations for amphipod moves
  165. fn valid_destinations(x: i64, y: i64, amphipods: &Vec<Amphipod>) -> Vec<(i64,i64)> {
  166.     let mut destinations: Vec<(i64,i64)> = Vec::new();
  167.  
  168.     let unit = amphipods.iter().find(|a| (a.x,a.y) == (x,y)).unwrap();
  169.  
  170.     // Handle unit in final room or final room ready for unit
  171.     if let Some(target) = room_target(&unit.species,&amphipods) {
  172.         if amphipod_room_column(&unit.species) == x { return destinations; } // Unit is already in its final room
  173.         if accessible(x,y,target.0,target.1,&amphipods) {
  174.             destinations.push(target);
  175.             return destinations;
  176.         }
  177.     }
  178.  
  179.     // If unit is currently in the wrong room, valid destinations are:
  180.     // - any accessible hallway spot (handled below)
  181.     // - the deepest cell in target room if target room is ready (handled above)
  182.     // If unit is already in hallway, the only valid spot is the target room (handled above)
  183.     if y != HALLWAY_ROW {
  184.         let (xmax,_) = map_limits();
  185.         for xnew in 1..xmax {
  186.             // Skip entryways
  187.             match xnew {
  188.                 ROOM_COLUMN_AMBER  |
  189.                 ROOM_COLUMN_BRONZE |
  190.                 ROOM_COLUMN_COPPER |
  191.                 ROOM_COLUMN_DESERT => { continue; },
  192.                 _ => {},
  193.             }
  194.  
  195.             // Check if no unit in spot and spot is reachable
  196.             if amphipods.iter().find(|a| (a.x,a.y) == (xnew,HALLWAY_ROW)).is_none() {
  197.                 if accessible(x,y, xnew,HALLWAY_ROW, &amphipods) {
  198.                     destinations.push((xnew,HALLWAY_ROW));
  199.                 }
  200.             }
  201.         }
  202.     }
  203.     destinations
  204. }
  205.  
  206. // Assess whether a room is ready for amphipods to move into, if so, return deepest cell in room
  207. fn room_target(room: &AmphipodSpecies, amphipods: &Vec<Amphipod>) -> Option<(i64,i64)> {
  208.     let (_,ymax) = map_limits();
  209.     let column = amphipod_room_column(&room);
  210.     let mut target = (column,ymax-1);
  211.     for space in ((HALLWAY_ROW+1)..ymax).rev() {
  212.         match amphipods.iter().find(|a| (a.x,a.y) == (column,space)) {
  213.             Some(amphi) if amphi.species != *room => { return None; },
  214.             Some(_) => { target = (column,space); }
  215.             None => { return Some((column,space)); },
  216.         }
  217.     }
  218.     Some(target)
  219. }
  220.  
  221. // Assess whether amphipods have moved into their final destinations
  222. fn map_complete(amphipods: &Vec<Amphipod>) -> bool {
  223.     for amphipod in amphipods {
  224.         if amphipod_room_column(&amphipod.species) != amphipod.x { return false; }
  225.     }
  226.     true
  227. }
  228.  
  229. fn solve(input: &str, debug: bool) {
  230.     let file = File::open(input).expect("Input file not found.");
  231.     let reader = BufReader::new(file);
  232.    
  233.     // Generate initial map
  234.     let mut amphipods: Vec<Amphipod> = Vec::new();
  235.     for (y,line) in reader.lines().enumerate() {
  236.         for (x,c) in line.unwrap().chars().enumerate() {
  237.             let (x,y) = (x as i64, y as i64);
  238.             match c {
  239.                 'A' | 'B' | 'C' | 'D' => amphipods.push(Amphipod::new(x,y,c)),
  240.                 _ => {},
  241.             }
  242.         }
  243.     }
  244.  
  245.     // Debug info
  246.     if debug {
  247.         println!("Initial map:");
  248.         print_map(&amphipods);
  249.     }
  250.    
  251.     // Solve
  252.     let mut heap: BinaryHeap<State> = BinaryHeap::new();
  253.     let start = State { amphipods: amphipods, energy: 0 };
  254.     heap.push(start);
  255.     while heap.len() > 0 {
  256.  
  257.         let state = heap.pop().unwrap();
  258.         // Amphipods are organized
  259.         if map_complete(&state.amphipods) {
  260.             print_map(&state.amphipods);
  261.             println!("Solution: {}", state.energy);
  262.             // Part 1: 14510
  263.             // Part 2: 49180
  264.             break;
  265.         }
  266.  
  267.         if debug {
  268.             print_map(&state.amphipods);
  269.             println!("Energy: {}", state.energy);
  270.         }
  271.  
  272.         // Move amphipods
  273.         for amphipod in &state.amphipods {
  274.             let (x,y) = (amphipod.x,amphipod.y);
  275.             for (newx,newy) in valid_destinations(x,y,&state.amphipods) {
  276.                 let mut new_map = state.amphipods.clone();
  277.                 let energy_delta = amphipod.energy_cost() * distance(x,y,newx,newy);
  278.                 let new_energy = state.energy + energy_delta;
  279.                 move_from_to(x,y,newx,newy, &mut new_map);
  280.                 let new_state = State { amphipods: new_map, energy: new_energy };
  281.                 heap.push(new_state);
  282.             }
  283.         }
  284.     }
  285. }
  286.  
  287. // Determine whether point (x1,y1) is accessible from point (x0,y0) by counting amphipods in the way
  288. fn accessible(x0: i64, y0: i64, x1: i64, y1: i64, amphipods: &Vec<Amphipod>) -> bool {
  289.     amphipods
  290.         .iter()
  291.         .filter(|a| (a.x,a.y) != (x0,y0))                               // exclude self
  292.         .filter(|a| (a.x == x0 && a.y <  y0) ||                         // above self in same room
  293.                     (a.x == x1 && a.y <= y1) ||                         // above target in target room
  294.                     ((x0..=x1).contains(&a.x) && a.y == HALLWAY_ROW) || // in the way in hallway
  295.                     ((x1..=x0).contains(&a.x) && a.y == HALLWAY_ROW))   // in the way in hallway
  296.         .count() == 0
  297. }
  298.  
  299. // Determine what neighboring cells can be moved to FOR DISTANCE CALCULATION
  300. // PURPOSES ONLY. Not valid for determining whether a point can be a unit's
  301. // final destination because room entryways are valid neighbors but not valid
  302. // final destinations.
  303. cached! { NEIGHBORS;
  304.     fn neighbors(x: i64, y: i64) -> Vec<(i64,i64)> = {
  305.         let mut destinations: Vec<(i64,i64)> = Vec::new();
  306.         let newpts = vec![(x-1,y  ),(x+1,y  ),(x  ,y-1),(x  ,y+1)];
  307.  
  308.         for newpt in newpts {
  309.             let map_point = AMPHI_MAP.get(&(newpt.0,newpt.1)).unwrap();
  310.             use MapType::*;
  311.             match map_point {
  312.                 OutsideMap | Wall => {}, // not valid
  313.                 Hallway | Entryway | Room => destinations.push(newpt),
  314.             }
  315.         }
  316.         destinations
  317.     }
  318. }
  319.  
  320. struct PointDistance {
  321.     x: i64,
  322.     y: i64,
  323.     d: usize,
  324. }
  325.  
  326. cached! { DISTANCE;
  327.     fn distance(x0: i64, y0: i64, x1: i64, y1: i64) -> usize = {
  328.         let (xmax,ymax) = map_limits();
  329.         let mut visited = vec![vec![false; ymax as usize]; xmax as usize];
  330.         visited[x0 as usize][y0 as usize] = true;
  331.  
  332.         // Insert first point
  333.         let mut q: VecDeque<PointDistance> = VecDeque::new();
  334.         let initial = PointDistance { x: x0, y: y0, d: 0 };
  335.         q.push_back(initial);
  336.  
  337.         // Queue
  338.         while q.len() > 0 {
  339.             let node = q.pop_front().unwrap();
  340.  
  341.             // Are we there yet
  342.             if (node.x,node.y) == (x1,y1) { return node.d; }
  343.  
  344.             // Explore neighbors
  345.             for neighbor in neighbors(node.x, node.y) {
  346.                 let (nx,ny) = (neighbor.0 as usize, neighbor.1 as usize);
  347.                 if visited[nx][ny] { continue; }
  348.                 visited[nx][ny] = true;
  349.                 let new_node = PointDistance { x: neighbor.0, y: neighbor.1, d: &node.d+1 };
  350.                 q.push_back(new_node);
  351.             }
  352.         }
  353.         std::usize::MAX
  354.     }
  355. }
  356.  
  357. // Debug: print picture of current amphipod map
  358. fn print_map(amphipods: &Vec<Amphipod>) {
  359.     let (xmax,ymax) = map_limits();
  360.     println!("Map:");
  361.     for y in 0..=ymax {
  362.         for x in 0..=xmax {
  363.             if let Some(unit) = amphipods.iter().find(|a| (a.x,a.y) == (x,y)) {
  364.                 print!("{}",unit);
  365.             } else {
  366.                 if let Some(cell) = AMPHI_MAP.get(&(x,y)) {
  367.                     print!("{}", cell);
  368.                 }
  369.             }
  370.         }
  371.         println!();
  372.     }
  373.     println!();
  374. }
  375.  
  376. // Return limits of map
  377. cached! { MAP_LIM;
  378.     fn map_limits() -> (i64,i64) = {
  379.         (AMPHI_MAP.keys().map(|k| k.0).max().expect("Failed to determine maximum x-dimension."),
  380.          AMPHI_MAP.keys().map(|k| k.1).max().expect("Failed to determine maximum y-dimension."))
  381.     }
  382. }
  383.  
  384. fn main() {
  385.     let args: Vec<String> = env::args().collect();
  386.     let filename = &args[1];
  387.     let debug = if args.len() >= 3 { &args[2] == "--debug" } else { false };
  388.     solve(filename, debug);
  389. }
  390.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement