For day 17, we are emulating a small processor. The processor has 4 registers, a, b, and c, and a program counter. The program is an array of instructions, each of which is an integer.
;;; -*- Lisp -*- (in-package "ADVENT2024/DAY17") (defstruct (machine (:type vector) (:conc-name machine/)) (pc 0) a b c (program (vector) :read-only t))
To read a machine from the input file, we build a keyword argument list
for the MAKE-MACHINE
function and then apply the
function:
(defun read-machine (filename) (apply #’make-machine (collect-append (choose (#M(lambda (line) (cond ((str:starts-with? "Register A:" line) (list :a (parse-integer (subseq line 11)))) ((str:starts-with? "Register B:" line) (list :b (parse-integer (subseq line 11)))) ((str:starts-with? "Register C:" line) (list :c (parse-integer (subseq line 11)))) ((str:starts-with? "Program:" line) (list :program (collect ’vector (choose (#Mdigit-char-p (scan ’string (subseq line 9))))))) (t nil))) (scan-file filename #’read-line))))))
To run the machine, we sit in a loop, reading the instruction at the
program counter, and then using an ECASE
to dispatch
to the appropriate operation. We symbol-macrolet
the
parts of an instruction so that instructions appear to be simple
assignments.
(defun run-machine (machine) (symbol-macrolet ((a (machine/a machine)) (b (machine/b machine)) (c (machine/c machine)) (pc (machine/pc machine)) (program (machine/program machine)) (immediate (svref program (1+ pc))) (argument (ecase immediate (0 0) (1 1) (2 2) (3 3) (4 a) (5 b) (6 c))) (next-instruction (progn (incf pc 2) (iter)))) (let ((output ’())) (let iter () (if (>= pc (length program)) (reverse output) (ecase (svref program pc) (0 (setf a (truncate a (expt 2 argument))) next-instruction) (1 (setf b (logxor b immediate)) next-instruction) (2 (setf b (mod argument 8)) next-instruction) (3 (if (zerop a) next-instruction (progn (setf pc immediate) (iter)))) (4 (setf b (logxor b c)) next-instruction) (5 (push (mod argument 8) output) next-instruction) (6 (setf b (truncate a (expt 2 argument))) next-instruction) (7 (setf c (truncate a (expt 2 argument))) next-instruction)))))))
For part 1, we simply run the machine as given in the input file and print the output as comma separated integers:
(defun part-1 () (format nil "~{~d~^,~}" (run-machine (read-machine (input-pathname)))))
For part 2, we seek an initial value of the A
register
that will cause the machine to output its own program.
We search for the value of A one digit at a time:
(defun get-machine-state (machine) (list (machine/pc machine) (machine/a machine) (machine/b machine) (machine/c machine))) (defun set-machine-state! (machine state) (setf (machine/pc machine) (first state) (machine/a machine) (second state) (machine/b machine) (third state) (machine/c machine) (fourth state))) (defun try-machine (machine state input-a) (set-machine-state! machine state) (setf (machine/a machine) input-a) (run-machine machine)) (defun pad-terms (terms size) (revappend (make-list (- size (length terms)) :initial-element 0) terms)) (defun from-octal (octal-digits) (fold-left (lambda (n digit) (+ (* n 8) digit)) 0 (reverse octal-digits))) (defun part-2 () (let* ((machine (read-machine (input-pathname))) (initial-state (get-machine-state machine)) (target (machine/program machine))) (let term-loop ((terms ’()) (index (1- (length target)))) (if (= index -1) (from-octal terms) (let digit-loop ((digit 0)) (if (> digit 7) (error "No solution") (let* ((padded (pad-terms (cons digit terms) (length target))) (output (try-machine machine initial-state (from-octal padded)))) (if (and (= (length output) (length target)) (= (elt output index) (svref target index))) (term-loop (cons digit terms) (1- index)) (digit-loop (1+ digit))))))))))
The outer iteration in part-2 is over the program instructions. If the index is -1, we have found the solution. Otherwise, we iterate over the digits 0-7, trying each one in turn. We pad the terms with zeros to make an octal input number, run the machine, and check the output. If the output matches the target, we move to the next term. Otherwise, we increment the digit.
No comments:
Post a Comment