|
1 | 1 | (defpackage :aoc/2024/17 #.cl-user::*aoc-use*)
|
2 | 2 | (in-package :aoc/2024/17)
|
3 | 3 |
|
4 |
| -#; |
5 |
| -(sb-ext:gc :full t) |
6 |
| - |
7 | 4 | (defun parse-input (&optional (strings (uiop:read-file-lines #P"src/2024/day17.txt")))
|
8 | 5 | (destructuring-bind (registers program) (split-sequence:split-sequence "" strings :test 'equal)
|
9 | 6 | (list (mapcan #'extract-positive-integers registers)
|
10 |
| - (coerce (extract-positive-integers (first program)) 'vector)))) |
| 7 | + (coerce (extract-positive-integers ~program.first) 'vector)))) |
11 | 8 | #+#:excluded (parse-input)
|
12 |
| -(defaccessor a (r) (accesses (car r))) |
13 |
| -(defaccessor b (r) (accesses (cadr r))) |
14 |
| -(defaccessor c (r) (accesses (caddr r))) |
15 | 9 |
|
16 |
| -; (defun literal (regs rand) |
17 |
| -; (declare (ignore regs)) |
18 |
| -; rand) |
| 10 | +(defaccessor a (r) (accesses ~r.car)) |
| 11 | +(defaccessor b (r) (accesses ~r.cadr)) |
| 12 | +(defaccessor c (r) (accesses ~r.caddr)) |
| 13 | + |
19 | 14 |
|
20 | 15 | (defun combo (regs rand)
|
21 | 16 | (ecase rand
|
22 | 17 | ((0 1 2 3) rand)
|
23 |
| - (4 (a regs)) |
24 |
| - (5 (b regs)) |
25 |
| - (6 (c regs)) |
| 18 | + (4 ~regs.a) |
| 19 | + (5 ~regs.b) |
| 20 | + (6 ~regs.c) |
26 | 21 | (7 (error "Unused"))))
|
27 | 22 |
|
28 |
| -(destructuring-bind (regs program) (parse-input) |
29 |
| - (let1 ip 0 |
30 |
| - (labels ((next () (prog1 (aref program ip) (incf ip)))) |
31 |
| - (format nil "~{~A~^,~}" |
32 |
| - (looping |
33 |
| - (while (array-in-bounds-p program ip) |
34 |
| - (let ((instr (next)) |
35 |
| - (rand (next))) |
36 |
| - (ecase instr |
37 |
| - (0 (setf (a regs) (floor (a regs) (expt 2 (combo regs rand))))) |
38 |
| - (1 (setf (b regs) (logxor (b regs) rand))) |
39 |
| - (2 (setf (b regs) (mod (combo regs rand) 8))) |
40 |
| - (3 (if (/= (a regs) 0) (setf ip rand))) |
41 |
| - (4 (setf (b regs) (logxor (b regs) (c regs)))) |
42 |
| - (5 (collect! (mod (combo regs rand) 8))) |
43 |
| - (6 (setf (b regs) (floor (a regs) (expt 2 (combo regs rand))))) |
44 |
| - (7 (setf (c regs) (floor (a regs) (expt 2 (combo regs rand))))) )))))))) |
45 |
| -6,1,6,4,2,4,7,3,5 |
46 |
| - |
47 |
| -(destructuring-bind (regs program) (parse-input) |
48 |
| - (let1 ip 0 |
49 |
| - (setf (a regs) 'x) |
50 |
| - (labels ((next () (prog1 (aref program ip) (incf ip)))) |
51 |
| - (format nil "~{~A~^,~}" |
52 |
| - (looping |
53 |
| - (let1 outd 0 |
54 |
| - (while (and (array-in-bounds-p program ip) |
55 |
| - (< outd (length program))) |
| 23 | +(defun run (&optional (input (parse-input))) |
| 24 | + (destructuring-bind (regs program) input |
| 25 | + (let1 ip 0 |
| 26 | + (labels ((next () (prog1 (aref program ip) (incf ip)))) |
| 27 | + (format nil "~{~A~^,~}" |
| 28 | + (looping |
| 29 | + (while (array-in-bounds-p program ip) |
56 | 30 | (let ((instr (next))
|
57 | 31 | (rand (next)))
|
58 | 32 | (ecase instr
|
59 |
| - (0 (setf (a regs) `(floor ,(a regs) (expt 2 ,(combo regs rand))))) |
60 |
| - (1 (setf (b regs) `(logxor ,(b regs) ,rand))) |
61 |
| - (2 (setf (b regs) `(mod ,(combo regs rand) 8))) |
62 |
| - (3 (dbgl (a regs)) (setf ip 0 (a regs) 'x) (continuable (break))) |
63 |
| - (4 (setf (b regs) `(logxor ,(b regs) ,(c regs)))) |
64 |
| - (5 (incf outd) (collect! (dbg `(mod ,(combo regs rand) 8)))) |
65 |
| - (6 (setf (b regs) `(floor ,(a regs) (expt 2 ,(combo regs rand))))) |
66 |
| - (7 (setf (c regs) `(floor ,(a regs) (expt 2 ,(combo regs rand))))) |
67 |
| - ))))))))) |
| 33 | + (0 (setf ~regs.a (floor ~regs.a (expt 2 (combo regs rand))))) |
| 34 | + (1 (setf ~regs.b (logxor ~regs.b rand))) |
| 35 | + (2 (setf ~regs.b (mod (combo regs rand) 8))) |
| 36 | + (3 (if (/= ~regs.a 0) (setf ip rand))) |
| 37 | + (4 (setf ~regs.b (logxor ~regs.b ~regs.c))) |
| 38 | + (5 (collect! (mod (combo regs rand) 8))) |
| 39 | + (6 (setf ~regs.b (floor ~regs.a (expt 2 (combo regs rand))))) |
| 40 | + (7 (setf ~regs.c (floor ~regs.a (expt 2 (combo regs rand))))) )))))))) ) |
| 41 | + |
| 42 | + |
| 43 | +;; By inspecting the input one could see that the program is just a plain loop, |
| 44 | +;; repeating the same instructions over and over again; the last instruction is |
| 45 | +;; usually a jnz/3, and before that you will usually find a out/5. Letting the |
| 46 | +;; program run you will notice two more things: that reg B and C always get |
| 47 | +;; reset somewhere at the beginning of each iteration (which means we can |
| 48 | +;; define what out/5 will output as a function of reg A at the beginning of the |
| 49 | +;; iteration); second, that reg A keeps on getting reduced via adv/0. |
| 50 | +;; |
| 51 | +;; For example, if we make X the content of reg A at the beginning of each |
| 52 | +;; iteration, my program will out/5 the result of the following expression: |
| 53 | +;; |
| 54 | +;; (MOD |
| 55 | +;; (LOGXOR (LOGXOR (LOGXOR (MOD X 8) 1) 4) |
| 56 | +;; (FLOOR X (EXPT 2 (LOGXOR (MOD X 8) 1)))) |
| 57 | +;; 8) |
| 58 | +;; |
| 59 | +;; Moreover, thanks to adv/0, the program will keep on reducing reg A using the |
| 60 | +;; following expression: |
| 61 | +;; |
| 62 | +;; (FLOOR X (EXPT 2 3)) |
| 63 | +;; |
| 64 | +;; How is this useful? Well, we need to find the initial value of reg A such |
| 65 | +;; that the program will output itself. Since all that matters is the value of |
| 66 | +;; reg A at the beginning of each iteration, we can start from the end, and |
| 67 | +;; figure out which value of reg A will generate the last operand; then, by |
| 68 | +;; reversing the effect of adv/0, we can try to figure out which value of reg |
| 69 | +;; A at the second last iteration will cause out/5 to output the last opcode; |
| 70 | +;; then, move on to the second last operand, until we get to the first opcode. |
| 71 | +;; |
| 72 | +;; For example: my program ends with "3,0". If we want the last iteration to |
| 73 | +;; output 0, we need to find a value of reg A, X, that satisfies: |
| 74 | +;; |
| 75 | +;; (= (MOD |
| 76 | +;; (LOGXOR (LOGXOR (LOGXOR (MOD X 8) 1) 4) |
| 77 | +;; (FLOOR X (EXPT 2 (LOGXOR (MOD X 8) 1)))) |
| 78 | +;; 8) |
| 79 | +;; 0) ; last operand of my program |
| 80 | +;; |
| 81 | +;; Now, we know the program should halt, which implies reg A should to be 0 at |
| 82 | +;; the end of the iteration; reg A gets updated via (FLOOR X (EXPT 2 3)), so if |
| 83 | +;; we reverse that, we will find ourselves to check all the values such that: |
| 84 | +;; |
| 85 | +;; (= (FLOOR X (EXPT 2 3)) |
| 86 | +;; 0) ; so the program halts |
| 87 | +;; |
| 88 | +;; This means reg A, i.e., X, at the beginning of the last iteration could |
| 89 | +;; only assume one of the following values: 0, 1, 2, 3, 4, 5, 6, 7. Which of these |
| 90 | +;; will cause out/5 to output 0? 5! |
| 91 | +;; |
| 92 | +;; Moving on, if we want the second last iteration to output 3 we need to find |
| 93 | +;; X such that: |
| 94 | +;; |
| 95 | +;; (= (MOD |
| 96 | +;; (LOGXOR (LOGXOR (LOGXOR (MOD X 8) 1) 4) |
| 97 | +;; (FLOOR X (EXPT 2 (LOGXOR (MOD X 8) 1)))) |
| 98 | +;; 8) |
| 99 | +;; 3) ; last opcode of my program |
| 100 | +;; |
| 101 | +;; In this case, all the possible values of reg A to check are all the values |
| 102 | +;; such that: |
| 103 | +;; |
| 104 | +;; (= (FLOOR X (EXPT 2 3)) |
| 105 | +;; 5) ; so it will out/5 the right thing during the next iteration |
| 106 | +;; |
| 107 | +;; Or, more explicitly: 40, 41, 42, 43, 44, 45, 46, 47. |
| 108 | + |
| 109 | +(defun find-program-specifics (&optional (input (parse-input))) |
| 110 | + (destructuring-bind (regs program) input |
| 111 | + (let1 ip 0 |
| 112 | + (setf ~regs.a 'x) |
| 113 | + (labels ((next () (prog1 (aref program ip) (incf ip)))) |
| 114 | + (let (out-expr adv-expr) |
| 115 | + (while (array-in-bounds-p program ip) |
| 116 | + (let ((instr (next)) |
| 117 | + (rand (next))) |
| 118 | + (ecase instr |
| 119 | + (0 (setf adv-expr `(floor ,~regs.a (expt 2 ,(combo regs rand))))) |
| 120 | + (1 (setf ~regs.b `(logxor ,~regs.b ,rand))) |
| 121 | + (2 (setf ~regs.b `(mod ,(combo regs rand) 8))) |
| 122 | + (3 :noop) |
| 123 | + (4 (setf ~regs.b `(logxor ,~regs.b ,~regs.c))) |
| 124 | + (5 (setf out-expr `(mod ,(combo regs rand) 8))) |
| 125 | + (6 (setf ~regs.b `(floor ,~regs.a (expt 2 ,(combo regs rand))))) |
| 126 | + (7 (setf ~regs.c `(floor ,~regs.a (expt 2 ,(combo regs rand)))))))) |
| 127 | + (values out-expr adv-expr)))))) |
| 128 | +#+#:excluded (find-program-specifics) |
| 129 | + |
68 | 130 |
|
69 |
| -; (MOD |
70 |
| -; (LOGXOR (LOGXOR (LOGXOR (MOD X 8) 1) 4) |
71 |
| -; (FLOOR X (EXPT 2 (LOGXOR (MOD X 8) 1)))) |
72 |
| -; 8) |
| 131 | +(defun find-reg-a-for-quine (&optional (input (parse-input)) &aux (program ~input.second)) |
| 132 | + (multiple-value-bind (out-expr adv-expr) (find-program-specifics input) |
| 133 | + (assert (equal adv-expr '(floor x (expt 2 3)))) |
| 134 | + (let1 out-fn (eval `(lambda (x) ,out-expr)) |
| 135 | + (let1 rev-program (~> program (coerce ~ 'list) reverse) |
| 136 | + (looping |
| 137 | + (labels ((recur (remaining x) |
| 138 | + (cond ((null remaining) (minimize! (floor x 8))) |
| 139 | + (t (if (= (funcall out-fn x) ~remaining.car) |
| 140 | + (dorangei (x1 (* x 8) (+ (* x 8) 7)) |
| 141 | + (recur ~remaining.cdr x1))))))) |
| 142 | + (dolist (x (iota 8)) |
| 143 | + (recur rev-program x)))))))) |
| 144 | +#+#:excluded (find-reg-a-for-quine) |
73 | 145 |
|
74 |
| -; (A REGS) (FLOOR X (EXPT 2 3)) |
75 | 146 |
|
76 |
| -(defun solves? (target x) |
77 |
| - (= (mod |
78 |
| - (logxor (logxor (logxor (mod x 8) 1) 4) |
79 |
| - (floor x (expt 2 (logxor (mod x 8) 1)))) |
80 |
| - 8) |
81 |
| - target)) |
| 147 | +(define-solution (2024 17) (input parse-input) |
| 148 | + (values (run input) |
| 149 | + (find-reg-a-for-quine input))) |
82 | 150 |
|
83 |
| -(destructuring-bind (regs program) (parse-input) |
84 |
| - (zapf program [reverse (coerce _ 'list)]) |
85 |
| - (looping |
86 |
| - (labels ((recur (program x) |
87 |
| - (cond ((null program) (minimize! (floor x 8))) |
88 |
| - (t (if (solves? (car program) x) |
89 |
| - (dorangei (x1 (* x 8) (+ (* x 8) 7)) |
90 |
| - (recur (cdr program) x1)))) |
91 |
| - (t (error "NEVER"))))) |
92 |
| - (dolist (x (iota 8)) |
93 |
| - (recur program x))))) |
94 |
| -1623801469161855; too high |
95 |
| -1623801469161808; too high |
96 |
| -202975183645226 |
| 151 | +(define-test (2024 17) ("6,1,6,4,2,4,7,3,5" 202975183645226)) |
0 commit comments