src/2021/days/day-14.lisp @ 2848a4548adf

2023/01 and 2022/01

Also start porting my test data to the new account, since Twitter imploded and
apparently it's impossible for a website to store a goddamn username and
password in The Year of Our Lord 2023 so everyone just outsources auth all
the time, ugh.
author Steve Losh <steve@stevelosh.com>
date Fri, 01 Dec 2023 11:05:43 -0500
parents cec05589a84c
children (none)
(advent:defpackage* :advent/2021/14)
(in-package :advent/2021/14)

(defun parse (stream &aux (template (read-line stream)))
  (values
    ;; Initial state { (a . b): 1, …}
    (frequencies (map 'list #'cons template (subseq template 1)) :test #'equal)
    ;; Last character
    (char template (1- (length template))) ; last char
    ;; Rules { pair: (new-left-pair . new-right-pair), …}
    (iterate
      (for line :in-stream stream :using #'read-line)
      (ppcre:register-groups-bind (((rcurry #'char 0) l r m))
          ("(.)(.) -> (.)" line)
        (collect-hash ((cons l r) (cons (cons l m) (cons m r))) :test #'equal)))))

(defun polymerize (polymer rules steps)
  (do-repeat steps
    (iterate
      ;; Unfortunately we can't walk the hash table because we're modifying it.
      (for (k . v) :in (alexandria:hash-table-alist polymer))
      (unless (zerop v)
        (for (l . r) = (gethash k rules))
        (incf (gethash l polymer 0) v)
        (incf (gethash r polymer 0) v)
        (decf (gethash k polymer 0) v))))
  polymer)

(defun single-counts (polymer last)
  (let ((counts (make-hash-table)))
    (maphash (lambda (k v) (incf (gethash (car k) counts 0) v)) polymer)
    (incf (gethash last counts 0))
    (alexandria:hash-table-values counts)))

(defun polydiff (polymer last)
  (multiple-value-call #'- (extrema #'> (single-counts polymer last))))

(define-problem (2021 14) (stream) (2584 3816397135460)
  (multiple-value-bind (template last rules) (parse stream)
    (values
      (polydiff (polymerize (alexandria:copy-hash-table template) rules 10) last)
      (polydiff (polymerize (alexandria:copy-hash-table template) rules 40) last))))

#; Scratch --------------------------------------------------------------------

;; old implementation with HAMTs
(defun parse (stream)
  (values
    (read-line stream)
    (iterate
      (with-result result = (make-hash-table))
      (for line :in-stream stream :using #'read-line)
      (ppcre:register-groups-bind (((rcurry #'char 0) l r m))
          ("(.)(.) -> (.)" line)
        (setf (gethash r (alexandria:ensure-gethash l result (make-hash-table))) m)))))

(defun dict-inc (dict key &optional (n 1))
  (hamt:dict-insert dict key (+ n (or (hamt:dict-lookup dict key) 0))))

(defun dict+ (dict1 dict2)
  (hamt:dict-reduce #'dict-inc dict1 dict2))

(defun polymerize (template rules steps)
  (iterate
    (with cache = (make-hash-table :test #'equal))
    (with counts = (hamt:empty-dict))
    (for l :in-string template)
    (for r :in-string template :from 1)
    (for chunk = (recursively ((l l) (r r) (steps steps))
                   (alexandria:ensure-gethash (list l r steps) cache
                     (if (zerop steps)
                       (hamt:empty-dict)
                       (let ((m (gethash r (gethash l rules))))
                         (dict-inc (dict+ (recur l m (1- steps))
                                          (recur m r (1- steps)))
                                   m))))))
    (setf counts (dict+ counts chunk))
    (returning (reduce #'dict-inc template :initial-value counts))))

(defun polydiff (polymer)
  (multiple-value-call #'- (extrema #'> (mapcar #'cdr (hamt:dict->alist polymer)))))