#!/usr/bin/env python3 from collections import defaultdict import dataclasses import sys from typing import DefaultDict, Generator, Sequence, Tuple import re from itertools import repeat import pprint SEG_PAT = re.compile(r"(?P[\d]+),(?P[\d]+) -> (?P[\d]+),(?P[\d]+)") @dataclasses.dataclass class Seg: x1: int y1: int x2: int y2: int @staticmethod def from_str(s: str) -> "Seg": match = SEG_PAT.match(s) assert match return Seg( int(match["x1"]), int(match["y1"]), int(match["x2"]), int(match["y2"]) ) def points(self, diag: bool) -> Generator[Tuple[int, int], None, None]: if self.x1 == self.x2: # We're going up and down if self.y1 > self.y2: lo = self.y2 hi = self.y1 else: lo = self.y1 hi = self.y2 yield from zip(repeat(self.x1), range(lo, hi + 1)) elif self.y1 == self.y2: # We're going side to side if self.x1 > self.x2: lo = self.x2 hi = self.x1 else: lo = self.x1 hi = self.x2 yield from zip(range(lo, hi + 1), repeat(self.y1)) elif diag: # Figure out the slope if self.x1 > self.x2: # right to left lox = self.x2 hix = self.x1 loy = self.y2 hiy = self.y1 else: lox = self.x1 hix = self.x2 loy = self.y1 hiy = self.y2 slope = float(hiy - loy) / float(hix - lox) yf = float(loy) for x in range(lox, hix + 1): yield (x, int(yf)) yf += slope else: # No support for diagonals return None def part1(segments: Sequence[Seg]): # Create a counting dict, we can probably avoid the matrix stuff points: DefaultDict[Tuple[int, int], int] = defaultdict(lambda: 0) for seg in segments: for point in seg.points(diag=False): points[point] += 1 # Figure out how many locations where at least 2 lines overlap count = len([overlap for overlap in points.values() if overlap > 1]) print(f"{count} overlaps >1") def part2(segments: Sequence[Seg]): # Create a counting dict, we can probably avoid the matrix stuff points: DefaultDict[Tuple[int, int], int] = defaultdict(lambda: 0) for seg in segments: for point in seg.points(diag=True): points[point] += 1 # Figure out how many locations where at least 2 lines overlap count = len([overlap for overlap in points.values() if overlap > 1]) print(f"{count} overlaps >1") segments = [Seg.from_str(line) for line in sys.stdin] print("Part 1") part1(segments) print("Part 2") part2(segments)