day9.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import os
  2. from functools import lru_cache
  3. with open(os.path.join(os.path.dirname(__file__), "example.txt")) as example:
  4. example_data = example.read().splitlines()
  5. with open(os.path.join(os.path.dirname(__file__), "input.txt")) as example:
  6. input_data = example.read().splitlines()
  7. def bench(part):
  8. import time
  9. def wrapper(*args, **kwargs):
  10. start = time.perf_counter()
  11. value = part(*args, **kwargs)
  12. print(f"\tevaluation time: {time.perf_counter() - start} s")
  13. return value
  14. return wrapper
  15. @bench
  16. def part1(data):
  17. points = [list(map(int, point.split(","))) for point in data]
  18. n = len(points)
  19. max_area = 0
  20. for i in range(n):
  21. x1, y1 = points[i]
  22. for j in range(i + 1, n):
  23. x2, y2 = points[j]
  24. dx = abs(x1 - x2)
  25. dy = abs(y1 - y2)
  26. area = (dx + 1) * (dy + 1)
  27. if area > max_area:
  28. max_area = area
  29. print(f"Part1: {max_area=}")
  30. @bench
  31. def part2(data):
  32. def orient(a, b, c):
  33. """orient of trinity points: >0 – left, <0 – right, 0 – on the same line."""
  34. (x1, y1), (x2, y2), (x3, y3) = a, b, c
  35. return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)
  36. def on_segment(a, b, c, incl=False):
  37. """point on the line ab"""
  38. (x1, y1), (x2, y2), (x3, y3) = a, b, c
  39. if incl:
  40. return min(x1, x2) <= x3 <= max(x1, x2) and min(y1, y2) <= y3 <= max(y1, y2)
  41. else:
  42. return min(x1, x2) < x3 < max(x1, x2) and min(y1, y2) < y3 < max(y1, y2)
  43. def segments_intersect(p1, p2, q1, q2, incl=False):
  44. """either cross lines p1-p2 и q1-q2"""
  45. o1 = orient(p1, p2, q1)
  46. o2 = orient(p1, p2, q2)
  47. o3 = orient(q1, q2, p1)
  48. o4 = orient(q1, q2, p2)
  49. # general case
  50. if o1 * o2 < 0 and o3 * o4 < 0:
  51. return True
  52. # partial cases: collinearity + adjoint
  53. if o1 == 0 and on_segment(p1, p2, q1, incl):
  54. return True
  55. if o2 == 0 and on_segment(p1, p2, q2, incl):
  56. return True
  57. if o3 == 0 and on_segment(q1, q2, p1, incl):
  58. return True
  59. if o4 == 0 and on_segment(q1, q2, p2, incl):
  60. return True
  61. return False
  62. def point_in_poly(x, y, poly):
  63. inside = False
  64. n = len(poly)
  65. for i in range(n):
  66. x1, y1 = poly[i]
  67. x2, y2 = poly[(i + 1) % n]
  68. if orient((x1, y1), (x2, y2), (x, y)) == 0 and on_segment((x1, y1), (x2, y2), (x, y), incl=True):
  69. return True
  70. # beam to the right from point (x,y)
  71. if (y1 > y) != (y2 > y):
  72. # x-coord cross with y
  73. x_inter = x1 + (x2 - x1) * (y - y1) / (y2 - y1)
  74. if x_inter >= x:
  75. inside = not inside
  76. return inside
  77. def rect_inside_polygon(p1, p2, poly, cache_func=None):
  78. x1, y1 = p1
  79. x2, y2 = p2
  80. if x1 == x2 or y1 == y2:
  81. return False
  82. xmin, xmax = (x1, x2) if x1 < x2 else (x2, x1)
  83. ymin, ymax = (y1, y2) if y1 < y2 else (y2, y1)
  84. r0 = (xmin, ymin)
  85. r1 = (xmin, ymax)
  86. r2 = (xmax, ymax)
  87. r3 = (xmax, ymin)
  88. rect = [r0, r1, r2, r3]
  89. if cache_func is None:
  90. def cache_func(xx, yy):
  91. return point_in_poly(xx, yy, poly)
  92. def is_vertex(pt):
  93. return pt == p1 or pt == p2
  94. for xx, yy in rect:
  95. if not is_vertex((xx, yy)):
  96. if not cache_func(xx, yy):
  97. return False
  98. # check crossing edges
  99. rect_edges = [(r0, r1), (r1, r2), (r2, r3), (r3, r0)]
  100. n = len(poly)
  101. for e in rect_edges:
  102. p_a, p_b = e
  103. for i in range(n):
  104. q_a = poly[i]
  105. q_b = poly[(i + 1) % n]
  106. if segments_intersect(p_a, p_b, q_a, q_b, False):
  107. return False
  108. return True
  109. # part2
  110. poly = [tuple(map(int, point.split(","))) for point in data]
  111. alpha = 0.1
  112. beta = 0.1
  113. n = len(poly)
  114. xs = [x for x, _ in poly]
  115. ys = [y for _, y in poly]
  116. min_x, max_x = min(xs), max(xs)
  117. min_y, max_y = min(ys), max(ys)
  118. width = max_x - min_x
  119. height = max_y - min_y
  120. # geometric center
  121. cx = sum(xs) / n
  122. cy = sum(ys) / n
  123. # turn to tuple
  124. poly_tuple = tuple(poly)
  125. @lru_cache(maxsize=None)
  126. def pip_cached(xx, yy):
  127. return point_in_poly(xx, yy, poly_tuple)
  128. # filter candidates
  129. candidates = []
  130. for i in range(n):
  131. x1, y1 = poly[i]
  132. for j in range(i + 1, n):
  133. x2, y2 = poly[j]
  134. dx = abs(x1 - x2) + 1
  135. dy = abs(y1 - y2) + 1
  136. if dx == 0 or dy == 0:
  137. continue
  138. # heuristic 1: take only reasonable size of sides
  139. if dx < alpha * width or dy < beta * height:
  140. continue
  141. # heuristic 2: points lie on the both side of center
  142. # in one quadrant for example
  143. if (x1 - cx) * (x2 - cx) > 0 and (y1 - cy) * (y2 - cy) > 0:
  144. continue
  145. area_est = dx * dy
  146. candidates.append((area_est, i, j))
  147. candidates.sort(key=lambda t: t[0], reverse=True)
  148. best = None # (area, p1, p2)
  149. for area_est, i, j in candidates:
  150. p1 = poly[i]
  151. p2 = poly[j]
  152. if rect_inside_polygon(p1, p2, poly_tuple, cache_func=pip_cached):
  153. area = area_est
  154. if best is None or area > best[0]:
  155. best = (area, p1, p2)
  156. if best is None:
  157. return None
  158. area, p1, p2 = best
  159. result = {"area": area, "p1": p1, "p2": p2}
  160. print(f"Part2: {result}")
  161. part1(example_data)
  162. part1(input_data)
  163. part2(example_data)
  164. part2(input_data)