98 lines
2.9 KiB
Python
Executable File
98 lines
2.9 KiB
Python
Executable File
#!/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<x1>[\d]+),(?P<y1>[\d]+) -> (?P<x2>[\d]+),(?P<y2>[\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)
|