Advent of Code 2025


My AoC 2025 solutions in Common Lisp.

Day #01🔗

Part #01🔗

(with-open-file (f #P"./01")
  (print
   (loop :for position := 50 :then (mod (funcall dirf position distance) 100)
         :for direction := (read-char f nil :eof)
         :until (eq direction :eof)
         :for dirf := (if (char= #\R direction) #'+ #'-)
         :for distance := (read f)
         :do (print (list :position position
                          :direction direction
                          :dirf dirf
                          :distance distance))
         :count (zerop position))))

Part #02🔗

(defconstant +wheel-size+ 100)

(with-open-file (f #P"./01")
  (let ((zeroes 0) (dial 50))
    (labels ((turn-dial (dir) (setf dial (mod (+ dial dir) +wheel-size+)))
             (check () (if (zerop dial) (incf zeroes)))
             (up    () (turn-dial +1) (check))
             (down  () (turn-dial -1) (check)))
      (loop :for direction := (read-char f nil :eof)
            :until (eq direction :eof)
            :for dirf := (if (char= #\R direction) #'up #'down)
            :for distance := (read f)
            :do (loop :for i :below distance
                      :do (funcall dirf))))
    (print zeroes)))

Day #02🔗

Part #01🔗

(defun invalid-id-p (num)
  (block nil
    (let* ((num-str (format nil "~a" num))
           (num-len (length num-str)))
      (if (oddp num-len) (return nil))
      (let* ((halfway-point (truncate num-len 2))
             (left 
               (str:substring 0 halfway-point num-str))
             (right
               (str:substring halfway-point num-len num-str)))
        (string= left right)))))

(let* ((input-string
         (uiop:with-input-file (f #P"./input")
           (uiop:slurp-stream-string f)))
       (input-data
         (mapcar
          (lambda (range)
            (mapcar #'parse-integer
                    (str:split #\- range)))
          (str:split
           #\,
           (str:trim input-string)))))
  (print
   (loop :for (from to) :in input-data
         :sum
         (loop :for id :from from :to to
               :if (invalid-id-p id)
                 :sum id))))

Part #02🔗

(defun invalid-id-p (num)
  (let* ((num-str (format nil "~a" num)))
    (not (zerop (cl-ppcre:count-matches "^(.*)\\1+$" num-str)))))

(let* ((input-string (str:from-file #P"./input"))
       (input-data
         (mapcar
          (lambda (range)
            (mapcar #'parse-integer
                    (str:split #\- range)))
          (str:split
           #\,
           (str:trim input-string)))))
  (print
   (loop :for (from to) :in input-data
         :sum
         (loop :for id :from from :to to
               :if (invalid-id-p id)
                 :sum id))))

Day #03🔗

Part #01🔗


(defun get-first-max-from (data start notlast)
  (loop :with (max max-idx)
        :for idx :from start :below (- (length data) (if notlast 1 0))
        :for char := (aref data idx)
        :if (or (not max) (char< max char))
          :do (setf max char max-idx idx)
        :finally (return
                   (values max max-idx))))

(let* ((data (str:from-file #P"input"))
       (banks (str:lines data)))
  (print
   (loop :for bank :in banks
         :for (first first-idx) := (multiple-value-list (get-first-max-from bank 0 t))
         :for second := (get-first-max-from bank (1+ first-idx) nil)
         :sum (parse-integer (format nil "~c~c" first second)))))

Part #02🔗

(defun get-first-max-from (data start end)
  (loop :with (max max-idx)
        :for idx :from start :below end
        :for char := (aref data idx)
        :if (or (not max) (char< max char))
          :do (setf max char max-idx idx)
        :finally (return
                   (values max max-idx))))

(defconstant +max-batteries+ 12)

(let* ((data (str:from-file #P"input"))
       (banks (str:lines data)))
  (print
   (loop :for bank :in banks
         :sum
         (print
          (parse-integer
           (format
            nil "~{~c~}"
            (loop :for battery :from 11 :downto 0
                  :for (char char-idx)
                    := (multiple-value-list
                        (get-first-max-from
                         bank
                         ;; I am aware this is ugly
                         (if char-idx
                             (1+ char-idx) 0)
                         (- (length bank) battery)))
                  :collect char)))))))

Day #04🔗

Common🔗

(defconstant +paper+ #\@)
(defvar *input*
  (let* ((input-str (str:from-file #P"input"))
         (input-split (str:lines input-str)))
    (make-array (list (length input-split)
                      (length (car input-split)))
                :initial-contents input-split
                :element-type 'character)))

Part #01🔗

(print
 (loop :with (height width) := (array-dimensions *input*)
       :for y :from 0 :below height
       :sum (loop :for x :from 0 :below width
                  :if (char= +paper+ (aref *input* y x))
                    :count (let ((neighbours
                                   (loop :for delta-y :from -1 :to 1
                                         :sum
                                         (loop
                                           :for delta-x :from -1 :to 1
                                           :unless (= 0 delta-x delta-y)
                                             :count (ignore-errors
                                                     (char=
                                                      +paper+
                                                      (aref *input*
                                                            (+ y delta-y)
                                                            (+ x delta-x))))))))
                             (> 4 neighbours)))))

Part #02🔗

(defun find-extractable ()
 (loop :with (height width) := (array-dimensions *input*)
       :for y :from 0 :below height
       :nconc (loop :for x :from 0 :below width
                    :if (char= +paper+ (aref *input* y x))
                      :if (let ((neighbours
                                       (loop :for delta-y :from -1 :to 1
                                             :sum
                                             (loop
                                           :for delta-x :from -1 :to 1
                                           :unless (= 0 delta-x delta-y)
                                             :count (ignore-errors
                                                     (char=
                                                      +paper+
                                                      (aref *input*
                                                            (+ y delta-y)
                                                            (+ x delta-x))))))))
                                 (> 4 neighbours))
                        :collect (list x y))))

(print
 (loop :for extracts := (find-extractable)
       :for len := (length extracts)
       :until (zerop len)
       :sum len
       :do (loop :for (x y) :in extracts
                 :do (setf (aref *input* y x) #\.))))

Day #05🔗

Common🔗

(defvar *lines*
  (str:lines
   (str:trim
    (str:from-file #P"input"))))

(defvar *ranges*
  (loop :for line := (pop *lines*)
        :until (str:emptyp line)
        :collect (mapcar #'parse-integer
                         (str:split "-" line))))

(defvar *nums*
  (mapcar #'parse-integer *lines*))

Part #01🔗

(defun in-range (num range)
  (<= (car range) num (cadr range)))

(defun in-ranges (num)
  (find-if
   (alexandria:curry #'in-range num)
   *ranges*))

(print (count-if #'identity (mapcar #'in-ranges *nums*)))

Part #02🔗

(defun merge-ranges ()
  (let ((sr (sort *ranges* #'< :key #'car)))
    (serapeum:with-collector (collect)
      (loop
        :with (cstart cend) := (car sr)
        :for (start end) :in (cdr sr)
        :finally (collect (list cstart cend))
        :do (log4cl:log-info cstart cend start end)
        :do (cond
              ((< cend start)
               (collect (list cstart cend))
               (setf cstart start
                     cend end))
              ((<= cstart start cend end)
               (setf cend end))
              ;; ((<= cstart start end cend)
              ;;  nil)
              )))))

(defun sum-ranges (ranges)
  (loop :for (start end) :in ranges
        :sum (1+ (- end start))))

(print (sum-ranges (print (merge-ranges))))

Day #06🔗

Part #01🔗

(defvar *nums*)
(defvar *ops*)

(let* ((lines
         (str:lines
          (str:from-file #P"input")))
       (nums
         (mapcar (lambda (line)
                   (mapcar #'parse-integer
                           (str:words line)))
                 (butlast lines))))
  (setf *nums*
        (make-array
         (list (length nums)
               (length (car nums)))
         :initial-contents nums)
        *ops*
        (mapcar
         #'find-symbol
         (str:words
          (car (last lines))))))

(print
 (iter
   (for y below (array-dimension *nums* 1))
   (for op in *ops*)
   (sum
    (iter
      (for x below (array-dimension *nums* 0))
      (reducing (aref *nums* x y) by op)))))

Part #02🔗

(defun transpose (arr)
  (let ((height (length arr))
        (width (length (aref arr 0))))
    (iter
      (for x below width)
      (collect
          (coerce
           (iter
             (for y below (1- height))
             (collect (aref (aref arr y) x)))
           'string)))))

(let* ((lines
         (str:lines
          (str:from-file #P"input")))
       (transposed-lines
         (transpose (apply #'vector lines)))
       (nums
         (mapcar
          (lambda (line)
            (mapcar #'parse-integer line))
          (split-sequence:split-sequence-if
           #'str:blank? transposed-lines)))
       (ops
         (mapcar
          #'find-symbol
          (str:words
           (car (last lines))))))
  (print
   (reduce #'+
    (mapcar
     (lambda (op nums) (apply op nums))
     ops nums))))

Day #07🔗

Common🔗

(defconstant +emitter+ #\S)
(defconstant +tachyon+ #\|)
(defconstant +space+ #\.)
(defconstant +splitter+ #\^)

(defvar *input*
  (let ((lines (str:lines
                (str:from-file #P"input"))))
    ;; Originally this had an :element-type however
    ;; Part 2 didn't so I just removed it in both
    (make-array (list (length lines) (length (car lines)))
                :initial-contents lines)))

(defvar *emitter-idx*)
(let ((first-line (make-array (array-dimension *input* 1)
                              :displaced-to *input*)))
  (setf
   *emitter-idx* (position +emitter+ first-line)
   (aref *input* 1 *emitter-idx*) +tachyon+))

Part #01🔗

(print
 (iter
   (for y from 1 below (array-dimension *input* 0))
   (sum
    (iter
      (for x below (array-dimension *input* 1))
      (case (aref *input* y x)
        ((#.+space+)
         (when (char= (aref *input* (1- y) x) +tachyon+)
           (setf (aref *input* y x) +tachyon+)))
        ((#.+splitter+)
         (when (char= (aref *input* (1- y) x) +tachyon+)
           (setf (aref *input* y (1+ x)) +tachyon+
                 (aref *input* y (1- x)) +tachyon+)
           (counting t))))))))

Part #02🔗

(iter
  (with last = (1- (array-dimension *input* 0)))
  (for x below (array-dimension *input* 1))
  (setf (aref *input* last x) 1))

(iter
  (for y from (1- (array-dimension *input* 0)) downto 1)
  (iter
    (for x below (array-dimension *input* 1))
    (when (and
           (eq +space+ (aref *input* y x))
           (typep (aref *input* (1+ y) x) 'number))
      (setf (aref *input* y x)
            (aref *input* (1+ y) x))))
  (iter
    (for x below (array-dimension *input* 1))
    (when (eq +splitter+ (aref *input* y x))
      (let ((l (aref *input* y (1+ x)))
            (r (aref *input* y (1- x))))
        (when (and (typep r 'number)
                   (typep l 'number))
          (setf (aref *input* (1- y) x) (+ l r)))))))

(print *input*)
(print (aref *input* 1 *emitter-idx*))

Day #08🔗

Today I have to thank varjagg for their cl-union-find library.

Common🔗

(defconstant +dims+ 3)

(defvar *input*
  (let ((lines
          (str:lines
           (str:from-file
            ;; #P"exa"
            #P"input"
            ))))
    (make-array
     (list (length lines) +dims+)
     :initial-contents
     (mapcar
      (lambda (line)
        (mapcar
         #'parse-integer
         (str:split #\, line)))
      lines)
     :element-type 'integer)))

(defun point-dist (x y)
  (sqrt
   (iter
     (for dim below +dims+)
     (sum
      (expt
       (- (aref *input* x dim)
          (aref *input* y dim))
       2)))))

(defstruct dist
  x y value)

(defvar point-cnt (array-dimension *input* 0))
(defvar dists
  (make-array
   (/ (expt point-cnt 2) 2)
   :fill-pointer 0))
   

(iter
  (for x below point-cnt)
  (iter
    (for y from (1+ x) below point-cnt)
    (for dist = (point-dist x y))
    (vector-push
     (make-dist
      :x x :y y :value dist)
     dists)))

Part #01🔗

(defconstant +n-largest+ 3)
(defconstant +n-shortest+ 1000)

(let* ((p (cl-uf:make-partition :test #'eq))
       (connections
         (subseq
          (sort dists #'< :key #'dist-value)
          0 +n-shortest+)))
  (iter
    (for d in-vector connections)
    (let ((sx
            (cl-uf:make-set
             p (dist-x d)))
          (sy
            (cl-uf:make-set
             p (dist-y d))))
      (unless (eq sx sy)
        (cl-uf:union p sx sy))))
  (let ((final-circuits
          (subseq
           (sort
            (mapcar
             (lambda (s)
               (coerce
                (cl-uf:collect-set
                 p s)
                'vector))
             (remove-duplicates
              (iter
                (for x below point-cnt)
                (collect
                    (cl-uf:make-set p x)))))
            #'> :key #'length)
           0 +n-largest+)))
    (print
     (reduce #'* final-circuits
             :key #'length))))

Part #02🔗

(defvar *connections-to-go* (1- (array-dimension *input* 0)))

(block ex
  (let* ((p (cl-uf:make-partition :test #'eq))
         (connections
           (sort dists #'< :key #'dist-value)))
    (iter
      (for d in-vector connections)
      (let ((sx
              (cl-uf:make-set
               p (dist-x d)))
            (sy
              (cl-uf:make-set
               p (dist-y d))))
        (unless (eq sx sy)
          (cl-uf:union p sx sy)
          (decf *connections-to-go*)
          (when (zerop *connections-to-go*)
            (print (*
                    (aref *input* (dist-x d) 0)
                    (aref *input* (dist-y d) 0)))
            (return-from ex)))))))

Day #09🔗

Today again, I used a library, this time for the geometry.

Common🔗

(defconstant +dims+ 2)

(defvar *input*
  (let ((lines
          (str:lines
           (str:from-file #P"input"))))
    (make-array
     (list (length lines) +dims+)
     :initial-contents
     (mapcar
      (lambda (line)
        (mapcar
         #'parse-integer
         (str:split #\, line)))
      lines)
     :element-type 'integer)))

(defun scalar-dist (x y)
  (1+ (abs (- x y))))

(defun point-dist (x y)
  (* (scalar-dist (aref *input* x 0)
                  (aref *input* y 0))
     (scalar-dist (aref *input* x 1)
                  (aref *input* y 1))))

Part #01🔗

(let* ((point-cnt (array-dimension *input* 0)))
  (print
   (iter
     (for x below point-cnt)
     (maximize
      (iter
        (for y from (1+ x) below point-cnt)
        (for dist = (point-dist x y))
        (maximize dist into m)
        (declare (fixnum m))
        (finally (return m)))))))

Part #02🔗

(defvar *world*
  (apply #'geometry:make-polygon-from-coords
         (coerce (make-array
                  (reduce #'* (array-dimensions *input*))
                  :displaced-to *input*) 'list)))

(defun coord-list (x y)
  (let ((x1 (aref *input* x 0))
        (y1 (aref *input* x 1))
        (x2 (aref *input* y 0))
        (y2 (aref *input* y 1)))
    (list x1 y1 x1 y2 x2 y1 x2 y2)))

(let* ((point-cnt (array-dimension *input* 0))
       (current-max 0))
  (iter
    (for x in (alexandria:shuffle
               (loop :for x :below point-cnt :collect x)))
    (iter
      (for y in (alexandria:shuffle
                 (loop :for y :from (1+ x) :below point-cnt :collect y)))
      (for dist = (point-dist x y))
      (when (> dist current-max)
        (incf checked-times)
        (ignore-errors
         (unless (geometry:polygon-difference
                  (apply #'geometry:make-polygon-from-coords
                         (coord-list x y))
                  *world*)
           (setf current-max dist))))))

  (print current-max))