Advertisement
Guest User

AoC 2021 - Day 23

a guest
Dec 28th, 2021
1,549
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.48 KB | None | 0 0
  1. """
  2. Advent of Code 2021 - Day 23
  3. https://adventofcode.com/2021/day/23
  4. """
  5.  
  6. import re
  7. from copy import deepcopy
  8. from queue import PriorityQueue
  9. from typing import Any, Dict, List
  10.  
  11. DAY = '23'
  12.  
  13. FULL_INPUT_FILE = f'../inputs/day{DAY}/input.full.txt'
  14. TEST_INPUT_FILE = f'../inputs/day{DAY}/input.test.txt'
  15.  
  16. PART_2_INSERTIONS = {
  17.     'A': ['D', 'D'],
  18.     'B': ['C', 'B'],
  19.     'C': ['B', 'A'],
  20.     'D': ['A', 'C'],
  21. }
  22.  
  23.  
  24. class Burrow:
  25.     AMPHIPOD_TYPES = {'A': 1, 'B': 10, 'C': 100, 'D': 1000}
  26.     PATHS = {
  27.         0: {'A': [0, 1], 'B': [0, 1, 2], 'C': [0, 1, 2, 3], 'D': [0, 1, 2, 3, 4]},
  28.         1: {'A': [1], 'B': [1, 2], 'C': [1, 2, 3], 'D': [1, 2, 3, 4]},
  29.         2: {'A': [2], 'B': [2], 'C': [2, 3], 'D': [2, 3, 4]},
  30.         3: {'A': [3, 2], 'B': [3], 'C': [3], 'D': [3, 4]},
  31.         4: {'A': [4, 3, 2], 'B': [4, 3], 'C': [4], 'D': [4]},
  32.         5: {'A': [5, 4, 3, 2], 'B': [5, 4, 3], 'C': [5, 4], 'D': [5]},
  33.         6: {'A': [6, 5, 4, 3, 2], 'B': [6, 5, 4, 3], 'C': [6, 5, 4], 'D': [6, 5]}
  34.     }
  35.  
  36.     def __init__(self, state: Dict = None, cost: int = 0):
  37.         self.state = deepcopy(state) if state else {}
  38.         self.cost = cost
  39.         self.state_hash = self._state_hash
  40.  
  41.     def __lt__(self, other):
  42.         return self.cost < other.cost
  43.  
  44.     @classmethod
  45.     def move_cost(cls, hallway_position: int, room_type: str, room_position: int,
  46.                   amphipod_type: str):
  47.         distance = 2 * len(cls.PATHS[hallway_position][room_type]) + room_position \
  48.                          - (1 if hallway_position in (0, 6) else 0)
  49.         return distance * cls.AMPHIPOD_TYPES[amphipod_type]
  50.  
  51.     @property
  52.     def _state_hash(self):
  53.         return hash(tuple(tuple(v) for k, v in sorted(self.state.items())))
  54.  
  55.     @property
  56.     def is_a_winner(self):
  57.         return all(all(b == a for b in self.state[a]) for a in self.AMPHIPOD_TYPES)
  58.  
  59.     @property
  60.     def possible_moves(self) -> List[Any]:
  61.         next_possible_states = []
  62.         for hall_pos in range(len(self.state['H'])):
  63.             amphipod_type = self.state['H'][hall_pos]
  64.             if amphipod_type and self.room_open(amphipod_type):
  65.                 if self.path_to_room_clear(hall_pos, amphipod_type):
  66.                     room_pos = self.next_spot_in_room(amphipod_type)
  67.                     new_burrow = Burrow(self.state, self.cost)
  68.                     new_burrow.move('H', hall_pos, amphipod_type, room_pos)
  69.                     return [new_burrow]
  70.         for room in self.AMPHIPOD_TYPES:
  71.             if not self.room_open(room):
  72.                 amphipod_type = [_ for _ in self.state[room] if _][0]
  73.                 room_pos = self.state[room].index(amphipod_type)
  74.                 for hall_pos in self.PATHS:
  75.                     if not self.state['H'][hall_pos] and self.path_to_room_clear(hall_pos, room):
  76.                         new_burrow = Burrow(self.state, self.cost)
  77.                         new_burrow.move(room, room_pos, 'H', hall_pos)
  78.                         next_possible_states.append(new_burrow)
  79.         return next_possible_states
  80.  
  81.     def move(self, from_room: str, from_pos: int, to_room: str, to_pos: int):
  82.         self.cost += self.move_cost(from_pos, to_room, to_pos, to_room) if from_room == 'H' \
  83.             else self.move_cost(to_pos, from_room, from_pos, self.state[from_room][from_pos])
  84.         self.state[to_room][to_pos] = self.state[from_room][from_pos]
  85.         self.state[from_room][from_pos] = None
  86.         self.state_hash = self._state_hash
  87.  
  88.     def room_open(self, room: str) -> bool:
  89.         return all(_ in (None, room) for _ in self.state[room])
  90.  
  91.     def next_spot_in_room(self, room: str) -> int:
  92.         return len(self.state[room]) - 1 - self.state[room][::-1].index(None)
  93.  
  94.     def path_to_room_clear(self, hallway_start: int, end_room_type: str) -> bool:
  95.         for position in self.PATHS[hallway_start][end_room_type]:
  96.             if position != hallway_start and self.state['H'][position]:
  97.                 return False
  98.         return True
  99.  
  100.  
  101. def find_path(burrow: Burrow) -> Burrow:
  102.     queue = PriorityQueue()
  103.     visited = set()
  104.     queue.put(burrow)
  105.  
  106.     while queue:
  107.         burrow = queue.get()
  108.         if burrow.is_a_winner:
  109.             return burrow
  110.         elif burrow.state_hash not in visited:
  111.             for possible_move in burrow.possible_moves:
  112.                 queue.put(possible_move)
  113.             visited.add(burrow.state_hash)
  114.  
  115.  
  116. def load_data(infile_path: str) -> Dict:
  117.     with open(infile_path, 'r', encoding='ascii') as infile:
  118.         c = [re.match(r'\W*#+(\w)#(\w)#(\w)#(\w)#+', l).groups() for l in infile.readlines()[2:4]]
  119.         return {
  120.             'H': [None] * 7,
  121.             'A': [c[0][0], c[1][0]],
  122.             'B': [c[0][1], c[1][1]],
  123.             'C': [c[0][2], c[1][2]],
  124.             'D': [c[0][3], c[1][3]],
  125.         }
  126.  
  127.  
  128. def part_1(infile_path: str) -> int:
  129.     start_map = load_data(infile_path)
  130.     result = find_path(Burrow(start_map))
  131.     return result.cost
  132.  
  133.  
  134. def part_2(infile_path: str) -> int:
  135.     start_map = load_data(infile_path)
  136.     for c in PART_2_INSERTIONS:
  137.         start_map[c] = [start_map[c][0]] + PART_2_INSERTIONS[c] + [start_map[c][1]]
  138.     result = find_path(Burrow(start_map))
  139.     return result.cost
  140.  
  141.  
  142. def show_moves(b):
  143.     for i in range(len(b)):
  144.         print(f'{i} : {b[i].cost} : {b[i].state}')
  145.  
  146.  
  147. if __name__ == '__main__':
  148.     part1_answer = part_1(FULL_INPUT_FILE)
  149.     print(f'Part 1: {part1_answer}')
  150.  
  151.     part2_answer = part_2(FULL_INPUT_FILE)
  152.     print(f'Part 2: {part2_answer}')
  153.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement