diff --git a/src/2024/day14.lisp b/src/2024/day14.lisp index 520fd09..e92f450 100644 --- a/src/2024/day14.lisp +++ b/src/2024/day14.lisp @@ -1,7 +1,13 @@ - (defpackage :aoc/2024/14 - (:use :cl :aoc :alexandria :trivia :lla) + (:use :cl :aoc :alexandria :lla :ppcre) (:export + #:parse-line + #:make-robot + #:quadrant + #:robot-at-position + #:robot-at-time + #:safety-factor + #:sample-text #:sample-data #:sample-data2 #:part1 @@ -10,9 +16,46 @@ (in-package :aoc/2024/14) +(defstruct robot px py vx vy) + +(defun robot-position (r) + (vector (robot-px r) (robot-py r))) + +(defun robot-velocity (r) + (vector (robot-vx r) (robot-vy r))) + +(defun robot-at-position (r p) + (let* ((v (robot-velocity r))) + (make-robot + :px (aref p 0) + :py (aref p 1) + :vx (aref v 0) + :vy (aref v 1) + ))) + +(defun robot-at-time (r n grid) + (let* ((p (robot-position r)) + (v (robot-velocity r)) + (np (aops:vectorize (p v grid) + (mod (+ p (* n v)) grid)))) + (make-robot + :px (aref np 0) + :py (aref np 1) + :vx (aref v 0) + :vy (aref v 1) + ))) (defun parse-line (line) - line) + (let ((numbers nil)) + (do-matches-as-strings + (s "-?\\d+" line) + (push (parse-integer s) numbers)) + (make-robot + :px (nth 3 numbers) + :py (nth 2 numbers) + :vx (nth 1 numbers) + :vy (nth 0 numbers) + ))) (defun parse-input (lines) @@ -21,15 +64,113 @@ (defparameter input-text (test-input 2024 14)) (defparameter input-data (parse-input input-text)) -(defparameter sample-text (aoc:split-lines "")) -(defparameter sample-data - (parse-input sample-text)) +(defparameter sample-text (aoc:split-lines "p=0,4 v=3,-3 +p=6,3 v=-1,-3 +p=10,3 v=-1,2 +p=2,0 v=2,-1 +p=0,0 v=1,3 +p=3,0 v=-2,-2 +p=7,6 v=-1,-3 +p=3,0 v=-1,-2 +p=9,3 v=2,3 +p=7,3 v=-1,2 +p=2,4 v=2,-3 +p=9,5 v=-3,-3 +")) -(defun part1 (data) - (length data)) +(defparameter sample-data + (parse-input sample-text)) + + +(defun quadrant (position gridsize) + (let ((mx (/ (1- (aref gridsize 0)) 2)) + (my (/ (1- (aref gridsize 1)) 2)) + (x (aref position 0)) + (y (aref position 1))) + (if (or (= x mx) (= y my)) + nil + (+ + (if (< x mx) 0 1) + (if (< y my) 0 2))))) + +(defun robots-at-time (robots n grid) + (mapcar + (lambda (r) (robot-at-time r n grid)) + robots)) + +(defun map-robots (robots gridsize) + + (let ((map (make-array (reverse (coerce gridsize 'list)) :initial-element 0))) + (loop + for r in robots + do (incf (aref map (robot-py r) (robot-px r)))) + map)) + +(defun show-robots (robots gridsize) + (defun render (n) + (if (= n 0) "." (format nil "~A" n))) + (let ((map (map-robots robots gridsize))) + (loop + for y from 0 below (array-dimension map 0) + do (loop + for x from 0 below (array-dimension map 1) + do (format t "~A" (render (aref map y x)))) + do (format t "~%")))) + +(defun safety-factor (robots grid) + + (let ((qs (make-array '(4) :initial-element 0)) + (rp-100 (lambda (r) (robot-at-time r 100 grid)))) + (loop + for p in (mapcar rp-100 robots) + for q = (quadrant (robot-position p) grid) + if q + do (incf (aref qs q)) + finally + (return (aops:vectorize-reduce #'* (qs) (identity qs)))))) + +(defun robots-symmetry-score (robots grid) + (let ((m (map-robots robots grid))) + (loop + for y from 0 below (array-dimension m 0) + sum (loop + for x1 from 0 below (/ (1- (array-dimension m 1)) 2) + for x2 from (1- (array-dimension m 1)) downto 0 + sum (if (and + (not (zerop (aref m y x1))) + (not (zerop (aref m y x2)))) + 1 0) + )))) + +(defun sqr (x) (* x x)) +(defun robots-variance-score (robots grid) + (let* ((n (length robots)) + (mx (/ (loop for r in robots sum (robot-px r)) n)) + (my (/ (loop for r in robots sum (robot-py r)) n)) + (vx (/ (loop for r in robots sum (sqr (- (robot-px r) mx))) n)) + (vy (/ (loop for r in robots sum (sqr (- (robot-py r) my))) n))) + (list (round mx) (round my) (sqrt vx) (sqrt vy)))) + +(defun find-t-with-symmetry-higher (max-t min-symmetry robots grid) + (loop for n from 0 to max-t + for score = (robots-symmetry-score (robots-at-time robots n grid) grid) + if (> score min-symmetry) + collect n)) + +(defun first-t-with-variance-lower (max-t max-s robots grid) + (loop for n from 0 to max-t + for score = (robots-variance-score (robots-at-time robots n grid) grid) + for sx = (nth 2 score) + for sy = (nth 3 score) + until (and (< sx max-s) (< sy max-s)) + finally (return (list n sx sy)))) + +(defun part1 (data &optional (grid #(101 103))) + (format nil "~A" (safety-factor data grid))) (defun part2 (data) - (length data)) + (let ((stats (first-t-with-variance-lower 50000 20 data #(101 103)))) + (format nil "~A" (first stats)))) (defun solve-day () (format t "part1: ~A~%" (part1 input-data)) diff --git a/tests/2024/day14-test.lisp b/tests/2024/day14-test.lisp index e744216..9d253cb 100644 --- a/tests/2024/day14-test.lisp +++ b/tests/2024/day14-test.lisp @@ -7,21 +7,42 @@ ;:parent suite-2024 ) -(define-test test-foo - :parent suite-2024-14 - ) +(define-test test-parse-line + :parent suite-2024-14 + (is equalp (make-robot :px 0 :py 4 :vx 3 :vy -3) (parse-line "p=0,4 v=3,-3" )) + ) +(define-test test-robot-position + :parent suite-2024-14 + (let ((r (make-robot :px 2 :py 4 :vx 2 :vy -3)) + (g #(11 7))) + (is equalp (robot-at-position r #(4 1)) (robot-at-time r 1 g)) + (is equalp (robot-at-position r #(6 5)) (robot-at-time r 2 g)) + (is equalp (robot-at-position r #(8 2)) (robot-at-time r 3 g)) + (is equalp (robot-at-position r #(10 6)) (robot-at-time r 4 g)) + (is equalp (robot-at-position r #(1 3)) (robot-at-time r 5 g)) + (is equalp (robot-at-position r #(4 5)) (robot-at-time r 100 g)) + )) -(define-test test-bar - :parent suite-2024-14 - ) +(define-test test-quadrant + :parent suite-2024-14 + (false (quadrant #(5 1) #(11 7))) + (false (quadrant #(8 3) #(11 7))) + (is = 0 (quadrant #(0 0) #(11 7))) + (is = 1 (quadrant #(7 2) #(11 7))) + (is = 2 (quadrant #(4 4) #(11 7))) + (is = 3 (quadrant #(6 4) #(11 7))) + ) + +(define-test test-safety-factor + :parent suite-2024-14 + (is = 12 (safety-factor sample-data #(11 7))) + ) (define-test+run test-part1 - :parent suite-2024-14 - (is equal nil (part1 sample-data))) + :parent suite-2024-14 + (is equal "12" (part1 sample-data #(11 7)))) + -(define-test+run test-part2 - :parent suite-2024-14 - (is equal nil (part2 sample-data)))