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

day13_part1.py (3664B)


      1 #!/usr/bin/env python
      2 """Advent of Code 2021, day 13 (part 1): Transparent Oragami
      3 Fold a grid of dots repeatedly to reveal a pattern"""
      4 
      5 # Another very numpy problem IMO. I've definitely become more comfortable with
      6 # numpy's interface, and how it differs from interacting with arrays and
      7 # matricies in R and MATLAB. So thanks, Advent of Code!
      8 
      9 from typing import Tuple, List
     10 from itertools import takewhile
     11 import re
     12 import numpy as np
     13 from utils import get_puzzle_input
     14 
     15 EXAMPLE_INPUT = \
     16 """6,10
     17 0,14
     18 9,10
     19 0,3
     20 10,4
     21 4,11
     22 6,0
     23 6,12
     24 4,1
     25 0,13
     26 10,12
     27 3,4
     28 3,0
     29 8,4
     30 1,10
     31 2,14
     32 8,10
     33 9,0
     34 
     35 fold along y=7
     36 fold along x=5
     37 """
     38 
     39 def parse_input(input_string: str) -> Tuple[np.ndarray, List[Tuple[int, int]]]:
     40     """Read the dot positions and folding instructions, and return a tuple containing:
     41     * A 2d boolean array of dots
     42     * A list of folding instructions, each consisting of a tuple giving the
     43     * axis (an int, 0 or 1) and position (also an int)"""
     44     # In numpy, axis 0 is rows, and 1 is columns. So a folding direction of y=7
     45     # tells us to fold *at* y=7 (looking at the example, we're supposed to use
     46     # zero-based indexing, so yay Python here I guess). I'll talk more about
     47     # folding later, but I just want to specify how folds will be specified:
     48     # axis (0 for a y fold, 1 for an x fold) and position.
     49 
     50     # I'm having fun with Python's generators: this will let us swallow up all
     51     # the coordinate lines, and then deal with the folding instructions later
     52     line_gen = (line for line in input_string.rstrip('\n').split('\n'))
     53 
     54     # Coordinates are given in x,y pairs and we'll change them to
     55     # numpy-friendly row,col order
     56     coordinates = np.flip(
     57         np.array([l.split(',') for l in takewhile(lambda x: x, line_gen)])
     58         .astype(int),
     59         axis=1
     60     )
     61 
     62     # Create the dot grid that we'll be folding
     63     dotgrid = np.zeros(np.apply_along_axis(max, 0, coordinates) + 1,
     64                        dtype=bool)
     65     dotgrid[coordinates[:,0], coordinates[:,1]] = True
     66 
     67     # Parse the folding instructions: we just want to yank out the axis (y = 0,
     68     # x = 1) and the value. Regex is overkill but I'm getting reaquainted with
     69     # python's funky re module here
     70     matches = (re.match(r"fold along (?P<axis>x|y)=(?P<value>\d+)", l) \
     71                for l in line_gen)
     72     instructions = [({'y': 0, 'x': 1}[m.group('axis')], int(m.group('value'))) \
     73                     for m in matches if m is not None]
     74 
     75     return (dotgrid, instructions)
     76 
     77 def fold_dotgrid(dotgrid: np.ndarray, instruction: Tuple[int, int]) -> np.ndarray:
     78     """Apply a fold (specified by axis--0 for rows, 1 for cols--and position)
     79     to the dotgrid, and return a new dotgrid"""
     80     # Hack: if we'll do all folds along axis 0, and if 1 is requested we'll
     81     # transpose the results before and after folding
     82     if instruction[0]:
     83         return np.transpose(
     84             fold_dotgrid(np.transpose(dotgrid), (0, instruction[1]))
     85         )
     86 
     87     # Ugh I hate doing slice math
     88     folded_grid = dotgrid.copy()[0:instruction[1], :]
     89     folded_grid[2*instruction[1] - dotgrid.shape[0] + 1:, :] |= np.flip(
     90         dotgrid[instruction[1]+1:, :],
     91         axis=0
     92     )
     93 
     94     return folded_grid
     95 
     96 def solve_puzzle(input_string: str) -> int:
     97     """Return the numeric solution to the puzzle"""
     98     dotgrid, instructions = parse_input(input_string)
     99     return fold_dotgrid(dotgrid, instructions[0]).sum()
    100 
    101 def main() -> None:
    102     """Run when the file is called as a script"""
    103     assert solve_puzzle(EXAMPLE_INPUT) == 17
    104     print("Number of dots visible after the first fold:",
    105           solve_puzzle(get_puzzle_input(13)))
    106 
    107 if __name__ == "__main__":
    108     main()