Wednesday, February 12, 2025

Advent of Code 2024: Day 2

For Day 2 in the Advent of Code, we are given a file where each line in the file contains a list of integers separated by spaces. We will be performing some manipulations on these lists.

First, we need to read the file. We make a function to read a line as a string, then read the integers from the string and collect the result into a list.

(defun read-levels (stream eof-error-p eof-value)
  (let ((line (read-line stream eof-error-p eof-value)))
    (if (eq line eof-value)
        eof-value
        (with-input-from-string (stream line)
          (collect ’list (scan-stream stream))))))

We use this function to read the entire file into a list of lists of integers.

(defun read-input (input-pathname)
  (collect ’list (scan-file input-pathname #’read-levels)))

We are concerned with the deltas between adjacent elements in a series of integers. We make a function to calculate the deltas. This will be an optimizable-series-function because it returns a series of deltas. We declare the argument to be an “off-line” input series as well. This code will be transformed into the equivalent loop code where we consume the deltas.

chunk is a series function that takes a series and produces n series of chunks that are offset by a specified amount. We produce chunks of size 2, offset by 1. This returns two series, the left number of each pair of numbers and the right number of each pair of numbers. By mapping #’- over these series, we get the series of deltas between adjacent numbers.

(defun deltas (series)
  (declare (optimizable-series-function)
           (off-line-port series))
  (multiple-value-bind (left right) (chunk 2 1 series)
    (#M- right left)))

As per the puzzle, a series of deltas is considered “safe” if it is strictly ascending or descending, and each step by which it ascends or descends is between 1 and 3 inclusive. We get the series of deltas, map #’plusp to get a series of booleans indicating whether each delta is positive, and collect-and on the series of booleans. Likewise with #’minusp for descending. Finally, we create a series of booleans indicating whether the absolute value of the delta is <= 3 and collect-and this as well. Whether the deltas are considered “safe” is just a boolean operation on these three boolean values:

(defun safe-levels? (list)
  (let ((deltas (deltas (scan list))))
    (let ((ascending (collect-and (#Mplusp deltas)))
          (descending (collect-and (#Mminusp deltas)))
          (small (collect-and (#M<= (Mmabs deltas) (series 3)))))
      (and small
           (or ascending descending)))))

The first part of the puzzle asks us to count the number of lines considered “safe”:

(defun part-1 ()
  (count-if #’safe-levels? (read-input (input-pathname))))

The second part of the puzzle relaxes the safety restriction by saying that you are allowed to ‘dampen’ the list by removing a single outlier before checking for safety.

(defun safe-dampened-levels? (levels)
  (find-if #’safe-levels? (remove-one-element levels)))

(defun part-2 ()
  (count-if #’safe-dampened-levels? (read-input (input-pathname))))

No comments: