10_asteroids.py 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. #!/usr/bin/env python3
  2. import sys
  3. from collections import defaultdict, deque
  4. from itertools import combinations
  5. from math import atan2, gcd, pi
  6. def read_asteroids(f):
  7. for y, line in enumerate(f):
  8. for x, c in enumerate(line):
  9. if c == '#':
  10. yield x, y
  11. def angle(dx, dy):
  12. return pi / 2 - atan2(dx, dy)
  13. def raytrace(asteroids):
  14. xmax = max(x for x, y in asteroids)
  15. ymax = max(y for x, y in asteroids)
  16. exists = set(asteroids)
  17. visible = defaultdict(dict)
  18. def trace(x, y, dx, dy):
  19. x += dx
  20. y += dy
  21. while 0 <= x <= xmax and 0 <= y <= ymax:
  22. if (x, y) in exists:
  23. return x, y
  24. x += dx
  25. y += dy
  26. for a, b in combinations(asteroids, 2):
  27. ax, ay = a
  28. bx, by = b
  29. dx = bx - ax
  30. dy = by - ay
  31. div = gcd(dx, dy)
  32. if trace(ax, ay, dx // div, dy // div) == b:
  33. visible[a][angle(dx, dy)] = b
  34. visible[b][angle(-dx, -dy)] = a
  35. return visible
  36. def shoot_laser(visible, station):
  37. targets = deque(sorted(visible[station].items()))
  38. while targets:
  39. angle, target = targets.popleft()
  40. yield target
  41. replacement = visible[target].get(angle, None)
  42. if replacement is not None:
  43. targets.append((angle, replacement))
  44. # part 1
  45. asteroids = list(read_asteroids(sys.stdin))
  46. visible = raytrace(asteroids)
  47. station = max(visible, key=lambda x: len(visible[x]))
  48. print(len(visible[station]))
  49. # part 2
  50. targets = shoot_laser(visible, station)
  51. for i in range(200):
  52. x, y = next(targets)
  53. print(x * 100 + y)