 My attempts to work through the 2021 Advent of Code problems.

```commit 08e1a707644c3e90cfbcc230a38b6ec8bae4de3a
parent ae50a2184f97659c33b8f5fdf8c09fa3616a6dde
Date:   Wed, 22 Dec 2021 15:42:56 -0500

Solution to day 22, part 1

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