mirror of
https://github.com/thib8956/advent-of-code.git
synced 2025-08-23 16:01:59 +00:00
115 lines
3.1 KiB
Python
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())
|
|
|