~shunter/advent2021

d9ce023b0b6fdee16ddbd9cc376004bd1a0c1836 — Samuel Hunter 3 years ago 81cc488
Refactor Day 14
1 files changed, 58 insertions(+), 54 deletions(-)

M day14.lisp
M day14.lisp => day14.lisp +58 -54
@@ 15,70 15,74 @@
          :for line := (read-line stream nil)
          :while line
          :for (pair result) := (split " -> " line)
          :for (a b) := (coerce pair 'list)
          :do (setf (gethash (cons a b) table) (char result 0))
          :do (setf (gethash (cons (char pair 0) (char pair 1))
                             table)
                    (char result 0))
          :finally (return table))))

(defparameter +input+
  (parse-lines #'identity))
(defun initial-polymer ()
  "Encode the template as a table of the count of element pairs."
  (loop :with pair-table := (make-hash-table :test 'equal)
        ;; Iterate across each pair of characters in the template, e.g. for the
        ;; template "NNCB", (a b) is bound to (#\N #\N), (#\N #\C), then (#\C
        ;; #\B).

(defun insert-pairs (template)
  (with-output-to-string (out)
    (loop :for (a b) :on (coerce template 'list)
          :while b
          :do (write-char a out)
              (write-char (gethash (cons a b) +rules+) out)
          :finally (write-char a out))))
        ;; By encoding the table as a count of all pairs, subsequent
        ;; generations only need to apply a rule twice for each unique pair,
        ;; instead of applying them once for each pair that exists.
        :for (a b) :on (coerce +template+ 'list)
        :while b

(defun count-elements (template)
  (loop :with dict := (make-hash-table)
        :for elem :across template
        :do (incf (gethash elem dict 0))
        :finally (return dict)))
        :do (incf (gethash (cons a b) pair-table 0))
        :finally (return pair-table)))

(defun freq-difference (dict)
  (loop :for elem :being :the :hash-keys :of dict
(defun polymerize (polymer)
  "Return a fresh pair table generated by applying the problem-set rules on
   each pair."
  (loop :with next-polymer := (make-hash-table :test 'equal)
        :for (a . b) :being :the :hash-keys :of polymer
        :using (hash-value count)
        :maximize count :into highest
        :minimize count :into lowest
        :finally (return (- highest lowest))))
        :for c := (gethash (cons a b) +rules+)

(defun solve-part-1 ()
  (loop :with template := +template+
        :repeat 10
        :do (setf template (insert-pairs template))
        :finally (return (freq-difference (count-elements template)))))
        :do (incf (gethash (cons a c) next-polymer 0) count)
            (incf (gethash (cons c b) next-polymer 0) count)
        :finally (return next-polymer)))

(defun template-table (template)
  (loop :with table := (make-hash-table :test 'equal)
        :for (a b) :on (coerce template 'list)
        :while b
        :do (incf (gethash (cons a b) table 0))
        :finally (return table)))
(defun count-elements (polymer)
  "Return a table of the count of all elements."
  (loop :with freq-table := (make-hash-table)
        :with first-elm := (char +template+ 0)
        :with last-elm := (char +template+ (1- (length +template+)))

(defun insert-pairs* (template-table)
  (loop :with next-generation := (make-hash-table :test 'equal)
        :for (a . b) :being :the :hash-keys :of template-table
        :for (a . b) :being :the :hash-keys :of polymer
        :using (hash-value count)
        :for c := (gethash (cons a b) +rules+)
        :do (incf (gethash (cons a c) next-generation 0) count)
            (incf (gethash (cons c b) next-generation 0) count)
        :finally (return next-generation)))
        ;; "Count" each element in each pair only half as often, as they appear
        ;; encoded as pairs twice as much than encoded as a string.
        :do (incf (gethash a freq-table 0) (/ count 2))
            (incf (gethash b freq-table 0) (/ count 2))

(defun count-elements* (template-table)
  (loop :with freq-dict := (make-hash-table)
        :with template := (coerce +template+ 'list)
        :for (a . b) :being :the :hash-keys :of template-table
        :using (hash-value count)
        :do (incf (gethash a freq-dict 0) (/ count 2))
            (incf (gethash b freq-dict 0) (/ count 2))
        :finally (progn
                   (incf (gethash (first template) freq-dict) 1/2)
                   (incf (gethash (car (last template)) freq-dict) 1/2)
                   (return freq-dict))))
        ;; Bump up the first and last elements, as they're only
        ;; counted once.
        :finally (incf (gethash first-elm freq-table) 1/2)
                 (incf (gethash last-elm freq-table) 1/2)
                 (return freq-table)))

(defun element-range (freq-table)
  "Return the difference of the frequencies of the most common and least common
   elements."
  (loop :for count :being :the :hash-values :of freq-table
        :maximize count :into highest
        :minimize count :into lowest
        :finally (return (- highest lowest))))

(defun solve (n-generations)
  (loop :with polymer := (initial-polymer)
        :repeat n-generations
        :do (setf polymer (polymerize polymer))
        :finally (return (element-range (count-elements polymer)))))

(defun solve-part-1 ()
  (solve 10))

(defun solve-part-2 ()
  (loop :with template := (template-table +template+)
        :repeat 40
        :do (setf template (insert-pairs* template))
        :finally (return (freq-difference (count-elements* template)))))
  (solve 40))