Skip to content

Commit 9e45426

Browse files
Explain 2024/17 and add input
1 parent 8f31f60 commit 9e45426

File tree

2 files changed

+131
-75
lines changed

2 files changed

+131
-75
lines changed

src/2024/day17.enc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(:C "vxZO1IYVCKM0jEMfPbr/8fhV2oGBQSzIPATdU+INB0WxNQ94TTKbI2870JEAUe5wA1LMstd67hYq8wrmO+/Zs1t4VQcBF/7ooH2YC0+e4UkMbfcpLwLiEkZMEQ==" :I "hdDy7dyDggSohAeiXupOlvxN4h4bo+oJl2spR8ZCdT0=" :G "82/dAOF1TVcg/uoxW5g2Iw==")

src/2024/day17.lisp

+130-75
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,151 @@
11
(defpackage :aoc/2024/17 #.cl-user::*aoc-use*)
22
(in-package :aoc/2024/17)
33

4-
#;
5-
(sb-ext:gc :full t)
6-
74
(defun parse-input (&optional (strings (uiop:read-file-lines #P"src/2024/day17.txt")))
85
(destructuring-bind (registers program) (split-sequence:split-sequence "" strings :test 'equal)
96
(list (mapcan #'extract-positive-integers registers)
10-
(coerce (extract-positive-integers (first program)) 'vector))))
7+
(coerce (extract-positive-integers ~program.first) 'vector))))
118
#+#:excluded (parse-input)
12-
(defaccessor a (r) (accesses (car r)))
13-
(defaccessor b (r) (accesses (cadr r)))
14-
(defaccessor c (r) (accesses (caddr r)))
159

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+
1914

2015
(defun combo (regs rand)
2116
(ecase rand
2217
((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)
2621
(7 (error "Unused"))))
2722

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)
5630
(let ((instr (next))
5731
(rand (next)))
5832
(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+
68130

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)
73145

74-
; (A REGS) (FLOOR X (EXPT 2 3))
75146

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)))
82150

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

Comments
 (0)