Day9.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // Run this by pasting it into the browser window of the URL "https://adventofcode.com/2025/day/9/input"
  2. // It is very slow, takeing multiple seconds
  3. {
  4. const response = await fetch(window.location.href)
  5. const input = await response.text()
  6. // const input = "7,1\n11,1\n11,7\n9,7\n9,5\n2,5\n2,3\n7,3"
  7. const points = []
  8. for (const line of input.split("\n")) {
  9. const [x, y] = line.split(",")
  10. if (x) {
  11. const point = [parseInt(x), parseInt(y)]
  12. points.push(point)
  13. }
  14. }
  15. let parity = 0
  16. {
  17. let p1 = points[points.length-2]
  18. let p2 = points[points.length-1]
  19. for (let i = 0; i < points.length; i++) {
  20. const p3 = points[i]
  21. if (p1[0] === p2[0] && p2[1] == p3[1]) {
  22. parity = parity - Math.sign((p2[1] - p1[1]) * (p3[0] - p2[0]))
  23. } else if (p1[1] === p2[1] && p2[0] == p3[0]) {
  24. parity = parity + Math.sign((p2[0] - p1[0]) * (p3[1] - p2[1]))
  25. }
  26. p1 = p2
  27. p2 = p3
  28. }
  29. }
  30. let rightIsInside = parity > 0;
  31. console.log(rightIsInside ? "Right is inside" : "Left is inside")
  32. let squares = []
  33. for (const [i, p1] of points.entries()) {
  34. for (let j = 0; j < i; j++) {
  35. const p2 = points[j]
  36. const size = (Math.abs(p1[0] - p2[0]) + 1) * (Math.abs(p1[1] - p2[1]) + 1)
  37. squares.push([size, p1, p2])
  38. }
  39. }
  40. squares.sort(([s1], [s2]) => s2 - s1)
  41. console.log("Answer part 1:", squares[0][0])
  42. function cornersToRange(c1, c2) {
  43. return square = [
  44. [Math.min(c1[0], c2[0]), Math.min(c1[1], c2[1])],
  45. [Math.max(c1[0], c2[0]), Math.max(c1[1], c2[1])]
  46. ]
  47. }
  48. function lineToVerticalRange(p1, p2) {
  49. if (p1[1] == p2[1]) {
  50. const lineParity = Math.sign(p2[0] - p1[0]) * parity
  51. return [
  52. [Math.min(p1[0], p2[0]), lineParity > 0 ? p1[1] : - Infinity],
  53. [Math.max(p1[0], p2[0]), lineParity > 0 ? Infinity : p1[1]]
  54. ]
  55. }
  56. return null
  57. }
  58. function intersect([lb1, ub1], [lb2, ub2]) {
  59. const intersection = [
  60. [Math.max(lb1[0], lb2[0]), Math.min(ub1[0], ub2[0])],
  61. [Math.max(lb1[1], lb2[1]), Math.min(ub1[1], ub2[1])]
  62. ]
  63. if (intersection[0][0] > intersection[1][0] || intersection[0][1] > intersection[1][1]) {
  64. return null
  65. }
  66. }
  67. function combineVerticalRange([lb1, ub1], [lb2, ub2]) {
  68. if (ub1[1] <= lb2[1] || ub2[1] <= lb1[1] || (ub1[1] > ub2[1] && lb1[1] < lb2[1]) || (ub2[1] > ub1[1] && lb2[1] < lb1[1])) {
  69. return [[[lb1, ub1]], [[lb2, ub2]], []]
  70. }
  71. const x1 = Math.max(lb1[0], lb2[0])
  72. const x2 = Math.min(ub1[0], ub2[0])
  73. if (x2 <= x1) {
  74. return [[[lb1, ub1]], [[lb2, ub2]], []]
  75. }
  76. const split1 = []
  77. const split2 = []
  78. const union = []
  79. if (lb1[0] < lb2[0]) {
  80. split1.push([
  81. [lb1[0], lb1[1]],
  82. [lb2[0], ub1[1]]
  83. ])
  84. } else if (lb2[0] < lb1[0]) {
  85. split2.push([
  86. [lb2[0], lb2[1]],
  87. [lb1[0], ub2[1]]
  88. ])
  89. }
  90. if (ub2[0] > ub1[0]) {
  91. split2.push([
  92. [ub1[0], lb2[1]],
  93. [ub2[0], ub2[1]]
  94. ])
  95. } else if (ub1[0] > ub2[0]) {
  96. split1.push([
  97. [ub2[0], lb1[1]],
  98. [ub1[0], ub1[1]]
  99. ])
  100. }
  101. if (lb1[1] < lb2[1]) {
  102. if (lb1[1] > -Infinity) {
  103. split1.push([
  104. [x1, lb1[1]],
  105. [x2, lb2[1]]
  106. ])
  107. }
  108. } else if (lb2[1] < lb1[1]) {
  109. if (lb2[1] > -Infinity) {
  110. split2.push([
  111. [x1, lb2[1]],
  112. [x2, lb1[1]]
  113. ])
  114. }
  115. }
  116. const y1 = Math.max(lb1[1], lb2[1])
  117. if (ub2[1] > ub1[1]) {
  118. if (ub2[1] < Infinity) {
  119. split2.push([
  120. [x1, ub1[1]],
  121. [x2, ub2[1]]
  122. ])
  123. }
  124. } else if (ub1[1] > ub2[1]) {
  125. if (ub1[1] < Infinity) {
  126. split1.push([
  127. [x1, ub2[1]],
  128. [x2, ub1[1]]
  129. ])
  130. }
  131. }
  132. const y2 = Math.min(ub1[1], ub2[1])
  133. union.push([
  134. [x1, y1],
  135. [x2, y2]
  136. ])
  137. return [split1, split2, union]
  138. }
  139. function splitByYBound([lb, ub], [lyb, uyb]) {
  140. if (lyb > ub[1] || uyb < lb[1]) {
  141. return [[[lb, ub]], null]
  142. }
  143. const outside = []
  144. if (lb[1] < lyb) {
  145. outside.push([
  146. [lb[0], lb[1]],
  147. [ub[0], lyb - 1]
  148. ])
  149. }
  150. if (ub[1] > uyb) {
  151. outside.push([
  152. [lb[0], uyb + 1],
  153. [ub[0], ub[1]]
  154. ])
  155. }
  156. const inside = [
  157. [lb[0], Math.max(lb[1], lyb)],
  158. [ub[0], Math.min(ub[1], uyb)]
  159. ]
  160. return [outside, inside]
  161. }
  162. function splitByXBound([lb, ub], [lxb, uxb]) {
  163. if (lxb > ub[0] || uxb < lb[0]) {
  164. return [[[lb, ub]], null]
  165. }
  166. const outside = []
  167. if (lb[0] < lxb) {
  168. outside.push([
  169. [lb[0], lb[1]],
  170. [lxb - 1, ub[1]]
  171. ])
  172. }
  173. if (ub[0] > uxb) {
  174. outside.push([
  175. [uxb + 1, lb[1]],
  176. [ub[0], ub[1]]
  177. ])
  178. }
  179. const inside = [
  180. [Math.max(lb[0], lxb), lb[1]],
  181. [Math.min(ub[0], uxb), ub[1]]
  182. ]
  183. return [outside, inside]
  184. }
  185. function subtractRange([lb1, ub1], [lb2, ub2]) {
  186. const [outsideY, insideY] = splitByYBound([lb1, ub1], [lb2[1], ub2[1]])
  187. if (insideY) {
  188. const [outsideX, _] = splitByXBound(insideY, [lb2[0], ub2[0]])
  189. outsideY.push(...outsideX)
  190. }
  191. return outsideY
  192. }
  193. function combineLists(ranges1, ranges2) {
  194. for (let i = 0; i < ranges1.length; i++) {
  195. for (let j = 0; j < ranges2.length; j++) {
  196. const range1 = ranges1[i]
  197. const range2 = ranges2[j]
  198. if (!range1) {
  199. break
  200. }
  201. if (range2) {
  202. const [split1, split2, union] = combineVerticalRange(range1, range2)
  203. split1.push(...union)
  204. ranges1[i] = split1.pop()
  205. ranges2[j] = split2.pop()
  206. ranges1.push(...split1)
  207. ranges2.push(...split2)
  208. }
  209. }
  210. }
  211. ranges1.push(...ranges2)
  212. return ranges1.filter((x) => !!x)
  213. }
  214. function subtractLists(ranges1, ranges2) {
  215. for (let j = 0; j < ranges2.length; j++) {
  216. const range2 = ranges2[j]
  217. for (let i = 0; i < ranges1.length; i++) {
  218. const range1 = ranges1[i]
  219. if (!range1) {
  220. break
  221. }
  222. if (range2) {
  223. const res = subtractRange(range1, range2)
  224. ranges1[i] = res.pop()
  225. ranges1.push(...res)
  226. }
  227. }
  228. }
  229. return ranges1.filter((x) => !!x)
  230. }
  231. let ranges = []
  232. let p1 = points[points.length-1]
  233. for (let i = 0; i < points.length; i++) {
  234. const p2 = points[i]
  235. const range = lineToVerticalRange(p1, p2)
  236. if (range) {
  237. ranges = combineLists(ranges, [range])
  238. }
  239. p1 = p2
  240. }
  241. for (const [s, c1, c2] of squares) {
  242. const empty = subtractLists([cornersToRange(c1, c2)], ranges)
  243. if (empty.length == 0) {
  244. console.log("Answer part 2:", s)
  245. break
  246. }
  247. }
  248. }