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()