code / advent / 2024/07.lisp

1(defpackage #:advent.2024.07
2  (:use #:cl #:advent.2024.00))
3(in-package #:advent.2024.07)
4
5;;; Part 1.
6;; Using + and *, find which test values (before the colon) can be solved with
7;; the remaining numbers. Add valid test values to get the answer.
8
9(defvar *example* "190: 10 19
103267: 81 40 27
1183: 17 5
12156: 15 6
137290: 6 8 6 15
14161011: 16 10 13
15192: 17 8 14
1621037: 9 7 18 13
17292: 11 6 16 20")
18
19(defun parse-string (str)
20  (loop for line in (uiop:split-string str :separator '(#\newline))
21        for numbers = (ppcre:split ":?\\s+" line)
22        collect (mapcar #'parse-integer numbers)))
23
24(defun compute1 (nums ops)
25  (reduce (lambda (a b)
26            (funcall (pop ops) a b))
27          nums))
28
29(defun enumerate-oplists (length)
30  (cond
31    ((zerop length) ())
32    ((= length 1) '((+) (*)))
33    (t
34     (flet ((num->oplist (n)
35              (loop for op across (map 'vector (alexandria:compose #'parse-integer #'string)
36                                       (format nil "~v,'0b" length n))
37                    collect (case op
38                              (0 '+)
39                              (1 '*)))))
40       (loop for n from 0 to (1- (expt 2 length))
41             collect (num->oplist n))))))
42
43(defun valid? (nums)
44  (let ((oplen (- (length nums) 2)))
45    (loop for ops in (enumerate-oplists oplen)
46          if (= (car nums) (compute1 (cdr nums) ops))
47            return (car nums)
48          finally (return 0))))
49
50(defun solve1 (&optional (input (input)))
51  (loop for line in (parse-string (uiop:read-file-string input))
52        sum (valid? line)))
53
54;;; Part 2.  A THIRD TYPE OF OPERATOR!
55;; The third op, ||, combines the digits from the left and right inputs into a
56;; single number --- e.g. 12 || 345 -> 12345.
57
58(defun enumerate-oplists2 (length)
59  (flet ((num->oplist (n)
60           (loop for op across (map 'vector (alexandria:compose #'parse-integer
61                                                                #'string)
62                                    (format nil "~3,v,'0R" length n))
63                 collect (case op
64                           (0 '+)
65                           (1 '*)
66                           (2 'glom))))) ; ||
67    (loop for n from 0 to (1- (expt 3 length))
68          collect (num->oplist n))))
69
70(defun glom (a b)
71  (parse-integer (format nil "~D~D" a b)))
72
73(defun valid2? (nums)
74  (let ((oplen (- (length nums) 2)))
75    (loop for ops in (enumerate-oplists2 oplen)
76          if (= (car nums) (compute1 (cdr nums) ops))
77            return (car nums)
78          finally (return 0))))
79
80(defun solve2 (&optional (input (input)))
81  (loop for line in (parse-string (uiop:read-file-string input))
82        sum (valid2? line)))
83
84;;; This is taking a lot longer to run --- more operations to run through.. as
85;;; it goes I should say that I *could've*, and probably *should've*, rewritten
86;;; all the non-2 functions to be more general, then passed in the specifics
87;;; with the solve(1,2) ... but whatever.