Files
advent-of-code/adventofcode/2019/day10/day10.py
2025-08-05 23:29:34 +02:00

115 lines
3.1 KiB
Python

import time
import math
import cmath
from itertools import combinations
from collections import defaultdict
from bisect import insort
def dist(a, b):
"euclidean distance between two points (as complex numbers)"
return abs(a - b)
def main(inp):
asteroids = set()
for y, l in enumerate(inp):
for x, c in enumerate(l):
if c == "#":
asteroids.add(complex(x,y))
base = part1(asteroids)
#base = 22+28j
# construct a list of asteroids for each angle, sorted by distance from the base
angles = defaultdict(list)
for a in asteroids:
if a == base:
continue
angle = (math.degrees(cmath.phase(a - base)) + 90) % 360
elt = (abs(a - base), a)
insort(angles[angle], elt)
i = 0
for angle in sorted(angles):
i += 1
a = angles[angle].pop(0)
if i == 200:
_, a = a
print("Part 2: ", int(a.real*100+a.imag))
break
def part1(asteroids):
"""
# ray cast solution not working
start_time = time.perf_counter()
sight = defaultdict(set)
for a, b in combinations(asteroids, 2):
if has_direct_sight_ray(a, b, asteroids):
sight[a].add(b)
sight[b].add(a)
end_time = time.perf_counter() - start_time
print(f"compute lines of sight ray {end_time:.4f}")
m = max(sight.items(), key=lambda x: len(x[1]))
print(m[0], len(m[1]))
"""
#start_time = time.perf_counter()
# construct line of sight for each asteroid
sight = defaultdict(set)
for a, b in combinations(asteroids, 2):
if has_direct_sight(a, b, asteroids):
sight[a].add(b)
sight[b].add(a)
#end_time = time.perf_counter() - start_time
#print(f"compute lines of sight {end_time:.4f}")
m = max(sight.items(), key=lambda x: len(x[1]))
#print(m[0], len(m[1]))
print("Part 1: ", len(m[1]))
return m[0]
def has_direct_sight(a, c, asteroids):
"""
Returns true if a can see c, i.e. they have no other asteroid between
them. An asteroid b is between a and c if:
dist(a,c) = dist(a,b) + dist(b,c).
"""
ac = dist(a, c)
eps = 0.00001 # avoid floating-point shenanigans
for b in asteroids: # check if any asteroid is blocking the view between a and c
if b == a or b == c:
continue
if abs(dist(a, b) + dist(b, c) - ac) <= eps:
return False
return True
def has_direct_sight_ray(a, c, asteroids):
# FIXME not working...
"""
Cast a ray from a in the direction of c and return true if there is no
obstacle between a and c
"""
if c.real < a.real:
a, c = c, a
angle = cmath.phase(c - a)
t = 0
x = a.real
y = a.imag
while True:
x = a.real + t * math.cos(angle)
y = a.imag + t * math.sin(angle)
t += 1
if x >= c.real or y >= c.imag:
return True
if complex(int(x), int(y)) in asteroids:
return False
assert False, "Unreachable"
if __name__ == "__main__":
import fileinput
main(fileinput.input())