;;6:51 ;;7:49 ;; (ql:quickload '(fiveam cl-ppcre arrows)) (defstruct (part (:copier nil)) x m a s) (defun part-sum (part) (with-slots (x m a s) part (+ x m a s))) (defun copy-part (range-part) (with-slots (x m a s) range-part (make-part :x (copy-tree x) :m (copy-tree m) :a (copy-tree a) :s (copy-tree s)))) (defun update-part (range-part slot val) (let ((new (copy-part range-part))) (setf (slot-value new slot) val) new)) (defun part-options (range-part) (reduce #'* '(x m a s) :key (lambda (v) (destructuring-bind (st . end) (slot-value range-part v) (1+ (- end st)))))) (defun parse-tests (line-rule) (loop for test in (cl-ppcre:split "," line-rule) collect (cl-ppcre:register-groups-bind ((#'read-from-string prop op val target)) ("\(\\w\)\(<|>\)\(\\d+\):\(\\w+\)" test) (list op prop val target)))) (defun parse-rule (line) (cl-ppcre:register-groups-bind ((#'read-from-string name) (#'parse-tests tests) (#'read-from-string default)) ("\(\\w+\){\(.*\),\(\\w+\)}" line) (list* name default tests))) (defun parse-file (filename) (let ((workflows (make-hash-table)) parts) (dolist (line (uiop:read-file-lines filename)) (cond ((string-equal line "")) ((eq #\{ (aref line 0)) (cl-ppcre:register-groups-bind ((#'parse-integer x m a s)) ("{x=\(\\d+\),m=\(\\d+\),a=\(\\d+\),s=\(\\d+\)}" line) (push (make-part :x x :m m :a a :s s) parts))) ((destructuring-bind (name . rule) (parse-rule line) (setf (gethash name workflows) rule))))) (values workflows parts))) (defun test-part (workflows part &optional (start 'in)) (destructuring-bind (default . tests) (gethash start workflows) (let ((out (or (loop for (op prop val target) in tests when (funcall op (slot-value part prop) val) do (return target)) default))) (cond ((eq 'A out) (part-sum part)) ((eq 'R out) 0) ((test-part workflows part out)))))) (defun solve (filename) (multiple-value-bind (workflows parts) (parse-file filename) (reduce #'+ parts :key (alexandria:curry #'test-part workflows)))) (defun final-options (solutions) (loop for (r p) on solutions by #'cddr when (eq r 'A) sum (part-options p))) (fiveam:test parts (fiveam:is (equal '((< S 537 GD) (> X 2440 R)) (parse-tests "s<537:gd,x>2440:R" ))) (fiveam:is (= 256000000000000 (final-options (list 'a (make-part :x (cons 1 4000) :m (cons 1 4000) :a (cons 1 4000) :s (cons 1 4000))))))) (fiveam:test solutions (fiveam:is (= 19114 (solve "eg-in"))) (fiveam:is (= 446517 (solve "input")))) (defun part-2 (workflows start range) (if (member start '(A R)) (list start range) (destructuring-bind (df . opts) (gethash start workflows) (let ((drops (loop for (op prop val target) in opts append (destructuring-bind (st . end) (slot-value range prop) (part-2 workflows target (if (eq '< op) (if (< end val) (copy-part range) (progn (setf (slot-value range prop) (cons val end)) (update-part range prop (cons st (1- val))))) (if (> st val) (copy-part range) (progn (setf (slot-value range prop) (cons st val)) (update-part range prop (cons (1+ val) end)))))))))) (append (part-2 workflows df range) drops))))) (defun solve2 (filename) (let ((workflows (parse-file filename))) (final-options (part-2 workflows 'in (make-part :x (cons 1 4000) :m (cons 1 4000) :a (cons 1 4000) :s (cons 1 4000)))))) (fiveam:test solutions2 (fiveam:is (= 167409079868000 (solve2 "eg-in"))) (fiveam:is (= 130090458884662 (solve2 "input")))) (fiveam:run!)