advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

day22_part1.py (3571B)


      1 #!/usr/bin/env python
      2 """Advent of Code 2021, day 22 (part 1): Reactor Reboot
      3 Apply on/off instructions to a 3D grid of cuboids"""
      4 
      5 # Ugh, the way this problem is written you just KNOW that Part 2 is going to
      6 # require an entirely different approach, but yesterday really burned me on
      7 # overthinking situations that didn't come up, so I'm going to be chill today.
      8 
      9 from typing import Tuple, List, NewType
     10 import re
     11 from functools import reduce
     12 
     13 # For part 1 we'll just represent everything in the -50..50 bounds explicitly.
     14 # I suspect part 2 will require a different approach!
     15 import numpy as np
     16 
     17 from utils import get_puzzle_input
     18 
     19 EXAMPLE_INPUT = \
     20 """on x=-20..26,y=-36..17,z=-47..7
     21 on x=-20..33,y=-21..23,z=-26..28
     22 on x=-22..28,y=-29..23,z=-38..16
     23 on x=-46..7,y=-6..46,z=-50..-1
     24 on x=-49..1,y=-3..46,z=-24..28
     25 on x=2..47,y=-22..22,z=-23..27
     26 on x=-27..23,y=-28..26,z=-21..29
     27 on x=-39..5,y=-6..47,z=-3..44
     28 on x=-30..21,y=-8..43,z=-13..34
     29 on x=-22..26,y=-27..20,z=-29..19
     30 off x=-48..-32,y=26..41,z=-47..-37
     31 on x=-12..35,y=6..50,z=-50..-2
     32 off x=-48..-32,y=-32..-16,z=-15..-5
     33 on x=-18..26,y=-33..15,z=-7..46
     34 off x=-40..-22,y=-38..-28,z=23..41
     35 on x=-16..35,y=-41..10,z=-47..6
     36 off x=-32..-23,y=11..30,z=-14..3
     37 on x=-49..-5,y=-3..45,z=-29..18
     38 off x=18..30,y=-20..-8,z=-3..13
     39 on x=-41..9,y=-7..43,z=-33..15
     40 on x=-54112..-39298,y=-85059..-49293,z=-27449..7877
     41 on x=967..23432,y=45373..81175,z=27513..53682
     42 """
     43 
     44 Instruction = NewType(
     45     'Instruction',
     46     Tuple[bool, Tuple[int, int], Tuple[int, int], Tuple[int, int]]
     47 )
     48 
     49 def parse_input(input_string: str) -> List[Instruction]:
     50     """Given the puzzle input, return a list of "on" (True) or "off" (False)
     51     instructions and their x, y, and z bounds"""
     52     range_pattern = r"(-?\d+)\.\.(-?\d+)"
     53     groups = [
     54         match.groups()
     55         for l in input_string.rstrip('\n').split('\n')
     56         if (match := re.match("^(on|off) x=" + range_pattern
     57                               + ",y=" + range_pattern
     58                               + ",z=" + range_pattern, l)) is not None
     59     ]
     60     return [Instruction((
     61         {'on': True, 'off': False}[x[0]],
     62         (int(x[1]), int(x[2])),
     63         (int(x[3]), int(x[4])),
     64         (int(x[5]), int(x[6]))
     65     )) for x in groups]
     66 
     67 def inside_initialization_area(instruction: Instruction) -> bool:
     68     """Returns True iff the instruction is fully inside the area -50..50 along
     69     all thre axes"""
     70     return all(-50 <= i <= 50 for p in instruction[1:4] for i in p)
     71 
     72 def apply_step(reactor: np.ndarray, instruction: Instruction) -> np.ndarray:
     73     """Apply the given step to the reactor, and return a new reactor state"""
     74     reactor_update = reactor.copy()
     75     reactor_update[
     76         (instruction[1][0]+50):(instruction[1][1]+51),
     77         (instruction[2][0]+50):(instruction[2][1]+51),
     78         (instruction[3][0]+50):(instruction[3][1]+51)
     79     ] = instruction[0]
     80     return reactor_update
     81 
     82 def reboot_reactor(instructions: List[Instruction]) -> np.ndarray:
     83     """Starting with a reactor that's turned off, apply all the steps to
     84     initialize the reactor"""
     85     return reduce(apply_step,
     86                   instructions,
     87                   np.zeros((101, 101, 101), dtype=bool))
     88 
     89 def solve_puzzle(input_string: str) -> int:
     90     """Return the numeric solution to the puzzle"""
     91     return int(reboot_reactor(parse_input(input_string)).sum())
     92 
     93 def main() -> None:
     94     """Run when the file is called as a script"""
     95     assert solve_puzzle(EXAMPLE_INPUT) == 590784
     96     print("Number of cubes turned on:",
     97           solve_puzzle(get_puzzle_input(22)))
     98 
     99 if __name__ == "__main__":
    100     main()