diff --git a/adventofcode/2019/day10/day10.py b/adventofcode/2019/day10/day10.py new file mode 100644 index 0000000..89fc6a5 --- /dev/null +++ b/adventofcode/2019/day10/day10.py @@ -0,0 +1,114 @@ +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()) +