|
| 1 | +(defpackage :aoc/2025/09 #.cl-user::*aoc-use*) |
| 2 | +(in-package :aoc/2025/09) |
| 3 | + |
| 4 | +(defun read-red-tiles (&optional (strings (uiop:read-file-lines #P"src/2025/day09.txt"))) |
| 5 | + (mapcar #'extract-integers strings)) |
| 6 | + |
| 7 | + |
| 8 | +(defun area (x1 y1 x2 y2) |
| 9 | + (* (1+ (- (max x1 x2) (min x1 x2))) |
| 10 | + (1+ (- (max y1 y2) (min y1 y2))))) |
| 11 | + |
| 12 | +(defun part1 (&optional (reds (read-red-tiles))) |
| 13 | + (looping |
| 14 | + (dosublists (((x1 y1) . reds2) reds) |
| 15 | + (doseq ((x2 y2) reds2) |
| 16 | + (maximize! (area x1 y1 x2 y2)))))) |
| 17 | + |
| 18 | + |
| 19 | +(defun compress-coordinates (reds) |
| 20 | + (flet ((unique&sorted (list &aux (list (remove-duplicates list))) |
| 21 | + (make-array (length list) :initial-contents (sort list #'<)))) |
| 22 | + (loop for (x y) in reds |
| 23 | + collect x into xx |
| 24 | + collect y into yy |
| 25 | + finally (return (list (unique&sorted xx) (unique&sorted yy)))))) |
| 26 | + |
| 27 | + |
| 28 | +(defun edge? (vv x y) |
| 29 | + (looping |
| 30 | + (loop |
| 31 | + for (x1 y1) in (cons (last-elt vv) vv) |
| 32 | + for (x2 y2) in vv |
| 33 | + do (cond ((= x x1 x2) (thereis! (<= (min y1 y2) y (max y1 y2)))) |
| 34 | + ((= y y1 y2) (thereis! (<= (min x1 x2) x (max x1 x2)))))))) |
| 35 | + |
| 36 | + |
| 37 | +;; We cast a horizontal ray to the right and count how many |
| 38 | +;; vertical edges of the polygon it crosses. An odd count means the point |
| 39 | +;; is inside. |
| 40 | +;; |
| 41 | +;; Details / caveats: |
| 42 | +;; |
| 43 | +;; - The polygon is assumed to be axis-aligned, so edges are either vertical |
| 44 | +;; or horizontal. Only vertical edges can intersect a horizontal ray. |
| 45 | +;; |
| 46 | +;; - Horizontal edges are ignored entirely since they contribute no crossings |
| 47 | +;; (the ray is parallel to them). |
| 48 | +;; |
| 49 | +;; - For each vertical edge, we check whether Y lies within that edge’s |
| 50 | +;; Y-span, using a half-open interval [ymin, ymax): |
| 51 | +;; (<= ymin y) and (< y ymax) |
| 52 | +;; This avoids double-counting when the ray passes exactly through a |
| 53 | +;; vertex shared by two vertical edges: only the “lower” edge includes |
| 54 | +;; the vertex; the “upper” edge does not. |
| 55 | +;; |
| 56 | +;; - We also require (< x x1) so that only edges strictly to the right of |
| 57 | +;; the test point count as intersections. Note: equality here is handled |
| 58 | +;; elsewhere (EDGE?), so we deliberately do not treat x = x1 as a crossing. |
| 59 | +;; |
| 60 | +;; The final parity of the crossing count (odd/even) determines the result. |
| 61 | +(defun inside? (vv x y) |
| 62 | + (oddp |
| 63 | + (looping |
| 64 | + (loop |
| 65 | + for (x1 y1) in (cons (last-elt vv) vv) |
| 66 | + for (x2 y2) in vv |
| 67 | + do (when (= x1 x2) |
| 68 | + (count! (and (<= (min y1 y2) y) |
| 69 | + (< y (max y1 y2)) |
| 70 | + (< x x1)))))))) |
| 71 | + |
| 72 | +(defun part2 (&optional (reds (read-red-tiles))) |
| 73 | + (destructuring-bind (xx yy) (compress-coordinates reds) |
| 74 | + (let1 grid (make-array (list (length yy) (length xx)) |
| 75 | + :element-type 'boolean |
| 76 | + :initial-element nil) |
| 77 | + (doseq ((i y) (enumerate yy)) |
| 78 | + (doseq ((j x) (enumerate xx)) |
| 79 | + (setf (aref grid i j) (and (or (edge? reds x y) |
| 80 | + (inside? reds x y)) |
| 81 | + t)))) |
| 82 | + (flet ((index-of (item vector) |
| 83 | + (binary-search 0 (length vector) (partial-1 #'<=> (aref vector _) item)))) |
| 84 | + (looping |
| 85 | + (dosublists (((x1 y1) . reds2) reds) |
| 86 | + (doseq ((x2 y2) reds2) |
| 87 | + (let1 valid (looping |
| 88 | + (dorangei (i (index-of (min y1 y2) yy) (index-of (max y1 y2) yy)) |
| 89 | + (dorangei (j (index-of (min x1 x2) xx) (index-of (max x1 x2) xx)) |
| 90 | + (always! (aref grid i j))))) |
| 91 | + (when valid |
| 92 | + (maximize! (area x1 y1 x2 y2))))))))))) |
| 93 | + |
| 94 | + |
| 95 | +(define-solution (2025 09) (reds read-red-tiles) |
| 96 | + (values (part1 reds) (part2 reds))) |
| 97 | + |
| 98 | +(define-test (2025 09) (4782268188 1574717268)) |
0 commit comments