advent-of-code/2019/d10/ex2/ex2.py

88 lines
2.3 KiB
Python
Raw Normal View History

2019-12-11 18:14:11 +01:00
#!/usr/bin/env python
import sys
from cmath import phase
from itertools import groupby
from math import gcd, pi
from pprint import pprint
from typing import NamedTuple, Set, Tuple
class Position(NamedTuple):
x: int
y: int
def pos_to_angle_dist(pos: Position) -> Tuple[float, float]:
cartesian = complex(*pos)
angle = phase(cartesian)
if angle < -pi / 2:
angle += 2.5 * pi
else:
angle += pi / 2
return (angle, abs(cartesian))
def main() -> None:
asteroids = [
Position(x, y)
for y, line in enumerate(sys.stdin.readlines())
for x, c in enumerate(line.rstrip())
if c == "#"
]
def count_spotted(x: int, y: int) -> int:
seen: Set[Position] = set()
ans = 0
radius = 1
while True:
def is_r_away(pos: Position) -> bool:
return max(abs(pos.x - x), abs(pos.y - y)) == radius
to_visit = list(filter(is_r_away, asteroids))
radius += 1
if len(to_visit) == 0:
break
for pos in to_visit:
rel = (pos.x - x, pos.y - y)
common = gcd(*rel)
rel = Position(*(a // common for a in rel))
if rel in seen:
continue # Already have an asteroid on this path
seen.add(rel)
ans += 1
return ans
# We need to find the observatory's position as a prerequisite
ans, orig = max((count_spotted(*pos), pos) for pos in asteroids)
print(f"({orig.x}, {orig.y}): {ans}")
def to_rel(p: Position) -> Position:
return Position(*(a - o for (a, o) in zip(p, orig)))
angle_dists = sorted(
(pos_to_angle_dist(to_rel(p)), p) for p in asteroids if p != orig
)
grouped_angle_dists = [
[val[1] for val in group]
for __, group in groupby(angle_dists, key=lambda x: x[0][0])
]
def find_n_th(n: int) -> Position:
assert 0 < n < len(asteroids) # Sanity check
while n >= len(grouped_angle_dists):
for group in grouped_angle_dists:
group.pop(0)
n -= 1
return grouped_angle_dists[n - 1][0]
x, y = find_n_th(200)
print(x * 100 + y)
if __name__ == "__main__":
main()