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