#!/usr/bin/env python3 import sys import dataclasses import re from typing import Sequence, Set, Tuple from copy import deepcopy FOLD_RE = re.compile(r"fold along (?P[xy])=(?P[0-9]+)") Point = Tuple[int, int] @dataclasses.dataclass class Fold: direction: str offset: int @staticmethod def from_str(s: str) -> "Fold": match = FOLD_RE.match(s) assert match return Fold(match["dir"], int(match["off"])) class Grid: def __init__(self, points: Set[Point]): self.rows = max(y for x, y in points) + 1 self.cols = max(x for x, y in points) + 1 self.points = points def apply_fold(self, fold: Fold): if fold.direction == "y": # Find all points that are below this line below = {(x, y) for x, y in self.points if y > fold.offset} # Find their new positions and then remove the old ones self.points -= below self.points |= {(x, self.rows - y - 1) for (x, y) in below} self.rows //= 2 else: # Find all points that are to the right of this line right = {(x, y) for x, y in self.points if x > fold.offset} # Find their new positions and then remove the old ones self.points -= right self.points |= {(self.cols - x - 1, y) for (x, y) in right} self.cols //= 2 def display(self) -> Sequence[str]: grid = [["." for _ in range(self.cols)] for _ in range(self.rows)] for x, y in self.points: grid[y][x] = "#" return ["".join(line) for line in grid] def part1(grid: Grid, folds: Sequence[Fold]): grid = deepcopy(grid) grid.apply_fold(folds[0]) print(len(grid.points)) def part2(grid: Grid, folds: Sequence[Fold]): for fold in folds: grid.apply_fold(fold) print("\n".join(grid.display())) points_str, folds_str = sys.stdin.read().split("\n\n") points = { (int(a), int(b)) for a, b in map(lambda line: line.split(","), points_str.split("\n")) } grid = Grid(points) folds = [Fold.from_str(line) for line in folds_str.split("\n")] print("Part 1") part1(grid, folds) print("Part 2") part2(grid, folds)