Advertisement
kupuguy

Advent of Code 2024 Day 15

Dec 15th, 2024
227
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.43 KB | Source Code | 1 0
  1. from pathlib import Path
  2.  
  3. TEST = """**REDACTED**"""
  4.  
  5.  
  6. def parse(input: str) -> tuple[set[complex], set[complex], complex, list[complex]]:
  7.     walls: set[complex] = set()
  8.     boxes: set[complex] = set()
  9.     robot: complex = 0
  10.     moves: list[complex] = []
  11.     directions = {"<": -1, ">": 1, "^": -1j, "v": 1j}
  12.  
  13.     for y, row in enumerate(input.splitlines()):
  14.         if row.startswith("#"):
  15.             for x, c in enumerate(row):
  16.                 match c:
  17.                     case "#":
  18.                         walls.add(complex(x, y))
  19.                     case "@":
  20.                         robot = complex(x, y)
  21.                     case "O":
  22.                         boxes.add(complex(x, y))
  23.                     case _:
  24.                         pass
  25.         elif row:
  26.             moves.extend([directions[c] for c in row])
  27.  
  28.     return walls, boxes, robot, moves
  29.  
  30.  
  31. def gpscoord(p: complex) -> int:
  32.     return int(100 * p.imag + p.real)
  33.  
  34.  
  35. def part1(input: str) -> int:
  36.     walls, boxes, robot, moves = parse(input)
  37.     for d in moves:
  38.         new_robot = robot + d
  39.         if new_robot in walls:
  40.             continue
  41.         if new_robot in boxes:
  42.             new_box = new_robot + d
  43.             while new_box in boxes:
  44.                 new_box += d
  45.             if new_box in walls:
  46.                 continue
  47.             boxes.add(new_box)
  48.             boxes.remove(new_robot)
  49.         robot = new_robot
  50.  
  51.     return sum(gpscoord(p) for p in boxes)
  52.  
  53.  
  54. assert part1(TEST) == 10092
  55.  
  56. INPUT = Path("input/day15.txt").read_text()
  57. part1_total = part1(INPUT)
  58. print(f"{part1_total=:,}")  # 1,437,174
  59.  
  60.  
  61. def parse2(input: str) -> tuple[set[complex], set[complex], complex, list[complex]]:
  62.     walls: set[complex] = set()
  63.     boxes: set[complex] = set()
  64.     robot: complex = 0
  65.     moves: list[complex] = []
  66.     directions = {"<": -1, ">": 1, "^": -1j, "v": 1j}
  67.  
  68.     for y, row in enumerate(input.splitlines()):
  69.         if row.startswith("#"):
  70.             for x, c in enumerate(row):
  71.                 match c:
  72.                     case "#":
  73.                         walls.add(complex(2 * x, y))
  74.                         walls.add(complex(2 * x + 1, y))
  75.                     case "@":
  76.                         robot = complex(2 * x, y)
  77.                     case "O":
  78.                         boxes.add(complex(2 * x, y))
  79.                     case _:
  80.                         pass
  81.         elif row:
  82.             moves.extend([directions[c] for c in row])
  83.  
  84.     return walls, boxes, robot, moves
  85.  
  86.  
  87. def push_updown(
  88.     direction: complex, box: complex, walls: set[complex], boxes: set[complex]
  89. ) -> tuple[bool, set[complex], set[complex]]:
  90.     can_move = True
  91.     nb = box + direction
  92.     add_boxes: set[complex] = {nb}
  93.     remove_boxes: set[complex] = {box}
  94.  
  95.     if nb in walls or nb+1 in walls:
  96.         return False, set(), set()
  97.     if nb in boxes:
  98.         c, a, r = push_updown(direction, nb, walls, boxes)
  99.         can_move = can_move and c
  100.         add_boxes |= a
  101.         remove_boxes |= r
  102.         return can_move, add_boxes, remove_boxes
  103.     if nb - 1 in boxes:
  104.         c, a, r = push_updown(direction, nb - 1, walls, boxes)
  105.         can_move = can_move and c
  106.         add_boxes |= a
  107.         remove_boxes |= r
  108.     if can_move and nb + 1 in boxes:
  109.         c, a, r = push_updown(direction, nb + 1, walls, boxes)
  110.         can_move = can_move and c
  111.         add_boxes |= a
  112.         remove_boxes |= r
  113.         return can_move, add_boxes, remove_boxes
  114.  
  115.     return can_move, add_boxes, remove_boxes
  116.  
  117.  
  118. def push_right(
  119.     box: complex, walls: set[complex], boxes: set[complex]
  120. ) -> tuple[bool, set[complex], set[complex]]:
  121.     nb = box + 1
  122.     if nb in walls or nb + 1 in walls:
  123.         return False, set(), set()
  124.     add_boxes: set[complex] = {nb}
  125.     remove_boxes: set[complex] = {box}
  126.     while nb + 1 in boxes:
  127.         if nb + 3 in walls:
  128.             return False, set(), set()
  129.         add_boxes.add(nb + 2)
  130.         remove_boxes.add(nb + 1)
  131.         nb += 2
  132.     return True, add_boxes, remove_boxes
  133.  
  134.  
  135. def push_left(
  136.     box: complex, walls: set[complex], boxes: set[complex]
  137. ) -> tuple[bool, set[complex], set[complex]]:
  138.     nb = box - 1
  139.     if nb in walls:
  140.         return False, set(), set()
  141.     add_boxes: set[complex] = {nb}
  142.     remove_boxes: set[complex] = {box}
  143.     while nb - 1 in boxes:
  144.         nb -= 1
  145.         if nb - 1 in walls:
  146.             return False, set(), set()
  147.         add_boxes.add(nb - 1)
  148.         remove_boxes.add(nb)
  149.         nb -= 1
  150.  
  151.     return True, add_boxes, remove_boxes
  152.  
  153.  
  154. def show(walls: set[complex], boxes: set[complex], robot: complex) -> None:
  155.     height = max(int(p.imag) for p in walls) + 1
  156.     width = max(int(p.real) for p in walls) + 1
  157.     for line in range(height):
  158.         points = []
  159.         for x in range(width):
  160.             p = complex(x, line)
  161.             points.append(
  162.                 "#"
  163.                 if p in walls
  164.                 else "["
  165.                 if p in boxes
  166.                 else "]"
  167.                 if p - 1 in boxes
  168.                 else "@"
  169.                 if p == robot
  170.                 else "."
  171.             )
  172.         print("".join(points))
  173.  
  174.  
  175. def part2(inp: str, debug:bool=False) -> int:
  176.     walls, boxes, robot, moves = parse2(inp)
  177.     nboxes = len(boxes)
  178.     for d in moves:
  179.         assert nboxes == len(boxes)
  180.         if debug: show(walls, boxes, robot)
  181.         if robot in boxes or robot-1 in boxes:
  182.             print("error!")
  183.             input()
  184.  
  185.         if debug:
  186.             print("\nMove:", {1:'>', -1: '<', -1j:'^', 1j: 'v'}[d])
  187.  
  188.         new_robot = robot + d
  189.         if new_robot in walls:
  190.             continue
  191.         c, a, r = True, set(), set()
  192.         if d.real == 0:
  193.             box = new_robot
  194.             if box in boxes:
  195.                 c, a, r = push_updown(d, box, walls, boxes)
  196.             elif box - 1 in boxes:
  197.                 c, a, r = push_updown(d, box - 1, walls, boxes)
  198.         elif d == 1:
  199.             # push right
  200.             if new_robot in boxes:
  201.                 c, a, r = push_right(new_robot, walls, boxes)
  202.         else:  # push left
  203.             if new_robot - 1 in boxes:
  204.                 c, a, r = push_left(new_robot - 1, walls, boxes)
  205.         if c:
  206.             robot = new_robot
  207.             boxes = (boxes - r) | a
  208.  
  209.     show(walls, boxes, robot)
  210.     return sum(gpscoord(p) for p in boxes)
  211.  
  212.  
  213. # TEST2 = """**REDACTED**"""
  214. # part2(TEST2, debug=True)
  215. # exit()
  216. assert part2(TEST) == 9021
  217. print(part2(INPUT)) # 1437468
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement