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