Advent of Code 2023

1. — Day 1: Trebuchet?! ---

1.1. Problem 1 : https://adventofcode.com/2023/day/1

(defun fetch-parse-line (line)
  (let ((numerals (remove-if-not #'(lambda (i) i)
                                 (mapcar #'digit-char-p
                                         (coerce line 'list)))))
    (cond ((eq numerals '()) 0)
          ((eq (length numerals) 1) (+ (* (car numerals) 10)
                                       (car numerals)))
          (t (+ (* 10 (car numerals))
                (car (last numerals)))))))

(defun solve (file-name)
  (with-open-file (stream file-name)
    (do ((acc 0 (+ acc (fetch-parse-line (read-line stream)))))
        ((not (listen stream)) acc))))

(solve "./corrup_calib.txt")

1.2. Problem 2 : https://adventofcode.com/2023/day/1#part2

  • can reuse the code, just have to parse and insert spelled-out digits as numerals before hand
(load "c:/Users/raj.patil/quicklisp/setup.lisp")
(ql:quickload "cl-ppcre")

(defun transform (replacements line)
  (dolist (replacement replacements line)
    (setf line (funcall replacement line))))

(defun process-line (line)
  (let ((numerals (remove-if-not #'(lambda (i) i)
                                 (mapcar #'digit-char-p
                                         (coerce line 'list)))))
    (cond ((eq numerals '()) 0)
          ((eq (length numerals) 1) (+ (* (car numerals) 10)
                                       (car numerals)))
          (t (+ (* 10 (car numerals))
                (car (last numerals)))))))

(defun parse-line (line)
  (let ((replaces '(("zero" . "z0e")
                    ("one" . "o1e")
                    ("two" . "t2o")
                    ("three" . "t3e")
                    ("four" . "f4r")
                    ("five" . "f5e")
                    ("six" . "s6x")
                    ("seven" . "s7n")
                    ("eight" . "e8t")
                    ("nine" . "n9e"))))
    (process-line
     (transform (mapcar #'(lambda (substitution)
                            #'(lambda (l) (cl-ppcre:regex-replace-all
                                           (car substitution)
                                           l
                                           (cdr substitution))))
                         replaces)
                line))))

(defun solve (file-name)
  (with-open-file (stream file-name)
    (format t
            "~%Sum: ~S"
            (do ((acc 0 (+ acc (parse-line (read-line stream)))))
                ((not (listen stream)) acc)))))

(solve "./corrup_calib.txt")
  • definitely an eventful first day
  • if you don't maintain the trailing and leading character: you miss out on replaces like "twoneighthreeight" being "21838"
  • inserting extra sentinel characters to maintain an implicit invariant (existence of other spelled out numerals) is something that isn't obvious at a first glance.

2. — Day 2: Cube Conundrum ---

(load "~/quicklisp/setup.lisp")
(ql:quickload :split-sequence)

(defmacro ssq (char sequence)
  `(split-sequence:split-sequence ,char ,sequence))

(defvar *qualify* '((red . 12)
                  (green . 13)
                  (blue . 14)))

(defun fetch-at-least (color visions)
  (cons color
      (apply #'max
             (mapcar #'cdr
                     (remove-if-not #'(lambda (vision)
                                        (eq (car vision) color))
                                    visions)))))

(defun parse-pulls (pulls)
  (let* ((visions (mapcar #'(lambda (vision)
                            (let* ((clean (string-trim " " vision))
                                   (val (read-from-string clean))
                                   (color (read-from-string (cadr (ssq #\Space clean)))))
                              (cons color val)))
                        (ssq #\, pulls))))
    (mapcar #'(lambda (color)
              (fetch-at-least color visions))
          '(red green blue))))

(defun valid-game-p (at-leasts)
  (reduce #'(lambda (a b)
            (and a b))
        (mapcar #'(lambda (color-at-least)
                    (>= (cdr (assoc (car color-at-least) *qualify*))
                        (cdr color-at-least)))
                at-leasts)
        :initial-value t))

(defun parse-line-part-1 (line)
  (let* ((base-split (ssq #\: line))
       (id (parse-integer (cadr (ssq #\Space (car base-split)))))
       (at-leasts (parse-pulls (substitute #\, #\; (cadr base-split))))
       (valid (valid-game-p at-leasts)))
    (if (valid-game-p at-leasts) id 0)))

(defun parse-line-part-2 (line)
  (let* ((base-split (ssq #\: line))
       (id (parse-integer (cadr (ssq #\Space (car base-split)))))
       (at-leasts (parse-pulls (substitute #\, #\; (cadr base-split)))))
    (apply #'* (mapcar #'cdr at-leasts))))

(defun solve-1 (filename)
  (with-open-file (stream filename)
    (do ((acc 0 (+ acc (parse-line-part-1 (read-line stream)))))
      ((not (listen stream)) acc))))

(defun solve-2 (filename)
  (with-open-file (stream filename)
    (do ((acc 0 (+ acc (parse-line-part-2 (read-line stream) ))))
      ((not (listen stream)) acc))))

3. — Day 3: Gear Ratios ---

;;load board into array ;;have virtual padding
;; :- function that returns a dot when out of bounds
;;for each symbol check surroundings (8 of them)
;;mark indices for processing where a number occurs
;;when processing an index, mark all the indices where the number extends
;;report accumulation

(defun load-board (file-name)
  (with-open-file (stream file-name)
    (do ((board '() (cons (coerce (read-line stream) 'list)
                          board)))
        ((not (listen stream)) (reverse board)))))

(defvar *repr* (load-board "./board.txt"))
(defvar *rows* (length *repr*))
(defvar *cols* (length (car *repr*)))

(defun paref (i j array &key (sentinel #\.))
  "sentinel padded bget"
  (let ((rows (array-dimension array 0))
        (cols (array-dimension array 1)))
   (labels ((out-index (index bound) (or (< index 0)
                                        (>= index bound)))
           (orow (row-num) (out-index row-num rows))
           (ocol (col-num) (out-index col-num cols)))
     (if (or (orow i)
             (ocol j))
         sentinel
         (aref array i j)))))

(defun symbol-p (char)
  (and (not (digit-char-p char) )
       (not (char= char #\.) )))

(defvar *board* (make-array `(,*rows*
                              *cols*)
                            :initial-contents *repr*))

(defun proc-setf (i j array val)
  (let ((rows (array-dimension array 0))
        (cols (array-dimension array 1)))
    (labels ((out-index (index bound) (or (< index 0)
                                          (>= index bound)))
             (orow (row-num) (out-index row-num rows))
             (ocol (col-num) (out-index col-num cols))
             (validator (i j) (digit-char-p (paref i j *board*))))
      (when (and (not (or (orow i)
                          (ocol j)))
                 (validator i j))
        (setf (aref array i j) val)))))

(defun mark-check-local (i j check-mask)
  (let ((marks `((,(- i 1)  ,(- j 1))
                 (,(- i 1)  ,j)
                 (,(- i 1)  ,(+ j 1))
                 (,i  ,(- j 1))
                 (,i  ,(+ j 1))
                 (,(+ i 1)  ,(- j 1))
                 (,(+ i 1)  ,j)
                 (,(+ i 1)  ,(+ j 1)))))
    (mapcar #'(lambda (mark)
                (proc-setf (car mark)
                           (cadr mark)
                           check-mask
                           T))
            marks)))

(defun make-check-mask (board)
  (let* ((rlen (array-dimension board 0))
         (clen (array-dimension board 1))
         (base-mask (make-array `(,rlen
                                  ,clen)
                                :initial-element nil)))
    (do ((i 0 (+ 1 i)))
        ((= i rlen) base-mask)
      (do ((j 0 (+ 1 j)))
          ((= j clen))
        (when (symbol-p (paref i j board))
          (mark-check-local i j base-mask))))))

(defvar *check-mask* (make-check-mask *board*))

(defun get-row (board row)
  (loop for i from 0 to (- (array-dimension board 1) 1)
        collect (aref board row i)))

(defun process-row (row)
  (let ((brow (get-row *board* row))
        (crow (get-row *check-mask* row)))
    (do ((i 0)
         (acc 0))
        ((>= i *cols*) acc)
      (if (nth i crow)
          (let ((lefts (do ((l (- i 1) (- l 1))
                            (left '() (cons (nth l brow) left)))
                           ((or (< l 0) (not (digit-char-p (nth l brow))))
                            left)))
                (rights (do ((r (+ i 1) (+ r 1))
                             (right '() (cons (nth r brow) right)))
                            ((or (= r *cols*) (not (digit-char-p (nth r brow))))
                             (reverse right)))))
            (incf acc (parse-integer (coerce (append lefts
                                                     (list (nth i brow))
                                                     rights)
                                             'string)))
            (incf i (+ (length rights) 1)))
          (incf i 1)))))

(defun process-board ()
  (apply #'+ (loop for row from 0 to (- *rows* 1)
                   collect (process-row row))))

(process-board)

;;each number needs to identified by an id
;;populate an id mask that stores number locations
;;when checking surrounding ids of a gear,
;;  report multiplication if exactly 2 unique detected

(defvar *val-hash* (make-hash-table))

(defmacro gethashf (k)
  "fetch hash"
  `(gethash ,k *val-hash*))

(defmacro sethashf (k v)
  "set hash form"
  `(setf (gethashf ,k) ,v))

(defvar *id-loc* (make-array `(,*rows*
                               *cols*)
                             :initial-element 0))

(defun update-id-locs (id indices)
  (dolist (index indices)
    (setf (aref *id-loc* (car index) (cdr index)) id)))

(defparameter *id-ctr* 0)

(defun mark-numbers (row)
  (let ((brow (get-row *board* row))
        (crow (get-row *check-mask* row)))
    (do ((i 0))
        ((>= i *cols*) *id-loc*)
      (if (nth i crow)
          (let* ((lefts (do ((l (- i 1) (- l 1))
                             (left '() (cons (nth l brow) left)))
                            ((or (< l 0) (not (digit-char-p (nth l brow))))
                             left)))
                 (rights (do ((r (+ i 1) (+ r 1))
                              (right '() (cons (nth r brow) right)))
                             ((or (= r *cols*) (not (digit-char-p (nth r brow))))
                              (reverse right))))
                 (val (parse-integer (coerce (append lefts
                                                     (list (nth i brow))
                                                     rights)
                                             'string)))
                 (mark-indices (mapcar #'(lambda (col)
                                           (cons row col))
                                       (loop for index
                                             from (- i (length lefts))
                                               to (+ i (length rights))
                                             collect index))))
            (incf *id-ctr*)
            (sethashf *id-ctr* val)
            (update-id-locs *id-ctr* mark-indices)
            (incf i (+ (length rights) 1)))
          (incf i 1)))))

(defun mark-board-ids ()
  (loop for r from 0 to (- *rows* 1)
        do (mark-numbers r)))

(mark-board-ids)

(defun compute-gear-ratios ()
  (labels ((fetch-marks (i j)
             `((,(- i 1) . ,(- j 1))
               (,(- i 1) . ,j)
               (,(- i 1) . ,(+ j 1))
               (,i . ,(- j 1))
               (,i . ,(+ j 1))
               (,(+ i 1) . ,(- j 1))
               (,(+ i 1) . ,j)
               (,(+ i 1) . ,(+ j 1)))))
    (apply #'+
           (loop for i
                 from 0 to
                        (- *rows* 1)
                 append (loop for j
                              from 0 to
                                     (- *cols* 1)
                              collect
                              (if (char= (aref *board* i j)
                                         #\*)
                                  (let ((ids (remove-if #'zerop (remove-duplicates
                                                                 (mapcar #'(lambda (mark)
                                                                             (paref (car mark)
                                                                                    (cdr mark)
                                                                                    *id-loc*
                                                                                    :sentinel 0))
                                                                         (fetch-marks i j))))))
                                    (if (= (length ids) 2)
                                        (apply #'* (mapcar #'(lambda (id)
                                                               (gethashf id))
                                                           ids))
                                        0))
                                  0))))))

(compute-gear-ratios)

4. — Day 4: Scratchcards ---

4.1. part 1

;; compute set intersection
;; return conditioned expt of 2
;; accumulate

(load "~/quicklisp/setup.lisp")
(ql:quickload :split-sequence)

(defmacro ssq (char sequence)
  `(split-sequence:split-sequence ,char ,sequence))

(defun listify (str)
  (format t "%~S" str)
  (mapcar #'(lambda (num) (read-from-string num))
          (remove-if #'(lambda (str) (string= "" str))
                     (ssq #\Space (string-trim " " str)) )))

(defun parse-card (card)
  (let* ((numbers (string-trim " " (cadr (ssq #\: (string-trim '(#\return) card)))))
         (winning (listify (car (ssq #\| numbers))))
         (present (listify (cadr (ssq #\| numbers))))
         (wins (intersection winning present)))
    (if wins
        (expt 2 (- (length wins) 1))
        0)))

(defun solve (file-name)
  (with-open-file (stream file-name)
    (do ((acc 0 (+ acc (parse-card (read-line stream)))))
        ((not (listen stream)) acc))))

4.2. part 2

;; maintain frequency array for all cards
;; init by 1
;; for card i, incf freq of next (number of wins @ i) by freq of i 
;; take care of overflows
;; report final frequency sum

(load "~/quicklisp/setup.lisp")
(ql:quickload :split-sequence)

(defmacro ssq (char sequence)
  `(split-sequence:split-sequence ,char ,sequence))

(defun listify (str)
  (mapcar #'(lambda (num) (read-from-string num))
          (remove-if #'(lambda (str) (string= "" str))
                     (ssq #\Space (string-trim " " str)))))

(defun parse-card (card)
  (let* ((numbers (string-trim " " (cadr (ssq #\: (string-trim '(#\return) card)))))
         (winning (listify (car (ssq #\| numbers))))
         (present (listify (cadr (ssq #\| numbers)))))
    (length (intersection winning present))))

(defun paref (array id)
  (unless (null id)
    (aref array id)))

(defun solve (file-name)
  (with-open-file (stream file-name)
    (let* ((owins (do ((only-wins '() (cons (parse-card (read-line stream))
                                           only-wins)))
                     ((not (listen stream)) (reverse only-wins))))
           (wins (mapcar #'(lambda (id win)
                                (cons id win))
                            (loop for i from 1 to (length owins)
                                  collect i)
                            owins))
           (freqs (progn
                   (make-array (+ (length wins) 1)
                              :initial-element 1) )))
      (do* ((head wins (cdr head))
            (id (caar head) (caar head))
            (id-wins (cdar head) (cdar head))
            (id-copies (paref freqs id) (paref freqs id)))
           ((null head) (- (reduce #'+ freqs) 1))
        (unless (zerop id-wins)
         (loop for i from (+ id 1) to (+ id id-wins)
              do
                 (when  (>= i  (length freqs))
                   (return))
                 (incf (aref freqs i) id-copies)))))))

5. — Day 5: If You Give A Seed A Fertilizer ---

(load "~/quicklisp/setup.lisp")
(ql:quickload :cl-ppcre)

(defmacro ssq (str sequence)
  `(cl-ppcre:split ,str ,sequence))

(defun read-and-parse-file (file-name)
  (with-open-file (stream file-name)
    (ssq (coerce '(#\Return #\Return) 'string)
         (apply #'concatenate 'string
                (loop for line = (read-line stream nil)
                      while line
                      collect line)))))

(defvar *from-chain* '(seed soil fertilizer water light temperature humidity))

(defparameter *lambda-hash* (make-hash-table))

(defun smap (map-line)
  (let* ((spec (ssq " " map-line))
         (sst (read-from-string (second spec)))
         (dst (read-from-string (first spec)))
         (rl (read-from-string (third spec))))
    #'(lambda (smap-request)
        (if (<= sst smap-request (+ sst rl -1))
            (+ dst (- smap-request sst))
            nil))))

(defun compile-smaps (smap-list)
  (let ((smappers smap-list))
    #'(lambda (map-request)
        (dolist (smapper smappers map-request)
          (let ((smapped (funcall smapper map-request)))
            (if smapped
                (return smapped)))))))

(defun build-map (from to mapper)
  (labels ((fetch-from () from)
           (fetch-to () to)
           (call (input) (funcall mapper input)))
    #'(lambda (message &optional input)
        (cond ((eq message 'from) (fetch-from))
              ((eq message 'to) (fetch-to))
              ((eq message 'call) (call input))
              (t (error "invalid message received"))))))

(defun parse-map-spec (map-spec)
  (let* ((title (car (ssq " " map-spec)))
         (a (read-from-string (car (ssq "-to-" title))))
         (b (read-from-string (cadr (ssq "-to-" title))))
         (smaps (mapcar #'smap (cdr (ssq #\Return map-spec)))))
    (build-map a b (compile-smaps smaps))))

(defun range (start len)
  (loop for i from start below (+ start len) 
        collect i))

(defun parse-seed-spec (seed-spec)
  (do* ((key-head seed-spec (cdr value-head))
        (value-head (cdr key-head) (cdr key-head))
        (seeds '() ))
       ((null key-head) seeds)
    (setf seeds (append (range (car key-head) (car value-head))
                        seeds) )))

(defun parse-spec-brute (file-name)
  (let* ((info (read-and-parse-file file-name))
         (seeds (parse-seed-spec
                 (mapcar #'read-from-string
                         (ssq " " (cadr (ssq ": " (car info)))))))
         (maps (mapcar #'parse-map-spec (cdr info))))
    (dolist (map maps)
      (setf (gethash (funcall map 'from) *lambda-hash*) map))
    seeds))

(defun soil-to-loc (soil-number)
  (let ((mappers (mapcar #'(lambda (from-sym)
                             (gethash from-sym *lambda-hash*))
                         *from-chain*))
        (passed soil-number))
    (loop for func in mappers
          do (setf passed (funcall func 'call passed)))
    passed))

(defun solve-brute (file-name)
  (clrhash *lambda-hash*)
  (apply #'min (mapcar #'soil-to-loc
                       (parse-spec-brute file-name))))

;; Optimization 1
;; report min soil number for a range
;; final report min of mins
;; do not build seed-range list on the get go

(defun parse-seed-spec-opt-1 (seed-spec)
  (do* ((key-head seed-spec (cdr value-head))
        (value-head (cdr key-head) (cdr key-head))
        (seeds '()))
       ((null key-head) (reverse seeds))
    (setf seeds (cons  (cons (car key-head)
                             (car value-head))
                       seeds))))

(defun parse-spec-opt-1 (file-name)
  (let* ((info (read-and-parse-file file-name))
         (seed-ranges (parse-seed-spec-opt-1
                       (mapcar #'read-from-string
                               (ssq " " (cadr (ssq ": " (car info)))))))
         (maps (mapcar #'parse-map-spec (cdr info))))
    (dolist (map maps)
      (setf (gethash (funcall map 'from) *lambda-hash*) map))
    seed-ranges))

(defun min-range-soil-to-loc (range-info)
  (let* ((start (car range-info))
         (len (cdr range-info))
         (local-min (apply #'min (mapcar #'soil-to-loc (range start len)))))
    (format t "~%local-min for ~S : ~S" range-info local-min)
    (force-output)
    local-min))

(defun solve-opt-1 (file-name)
  (clrhash *lambda-hash*)
  (let ((seed-ranges (parse-spec-opt-1 file-name)))
    (format t "~%final min: ~S"
            (apply #'min (mapcar #'min-range-soil-to-loc
                         seed-ranges)))))
Tags::project: