On day 25, we are given a set of locks and keys as ascii art. A typical lock looks like this:
..... .#... .##.# .##.# ###.# ##### #####
and a typical key looks like this:
##### ##### ##.#. ##.#. ##.#. #..#. .....
We read the input file with a little state machine that accumulates lines until a blank line or end of file is reached. It decides whether what it read was a lock or a key by looking to see if the first row is all #'s or not. If it is, it's a key, otherwise it's lock.
(defun read-input (pathname) (let ((package (find-package "ADVENT2024/DAY25"))) (with-open-file (stream pathname) (let iter ((line (read-line stream nil)) (accum '()) (locks '()) (keys '())) (if line (let ((char-list (map 'list (lambda (c) (intern (string c) package)) line))) (if (null char-list) (let ((item (make-grid (length accum) (length (first accum)) :initial-contents (reverse accum)))) (if (every (lambda (s) (eq s '\#)) (first accum)) (iter (read-line stream nil) '() locks (cons item keys)) (iter (read-line stream nil) '() (cons item locks) keys))) (iter (read-line stream nil) (cons char-list accum) locks keys))) (let ((item (make-grid (length accum) (length (first accum)) :initial-contents (reverse accum)))) (if (every (lambda (s) (eq s '\#)) (first accum)) (values (reverse locks) (reverse (cons item keys))) (values (reverse (cons item locks)) (reverse keys)))))))))
A key fits into a lock (but doesn't necessarily open it) if none of
the '#
's in the key overlap with the '#
's
in the lock. This is easily checked by iterating over the key and
lock in parallel and ensuring that at least one of the characters is
'.
'.
(defun fits? (key lock) (collect-and (#M(lambda (k l) (or (eql k '|.|) (eql l '|.|))) (scan 'array key) (scan 'array lock))))
For part 1, we are asked to find the number of key/lock
combinations that result in a fit. We use map-product
from the alexandria
library to map
the fits?
predicate over the cartesian product of keys
and locks. We then count the number of fits.
(defun part-1 () (multiple-value-bind (locks keys) (read-input (input-pathname)) (count t (map-product #'fits? keys locks))))
There is no part 2 for this problem.
We've arrived at the end of the 2024 Advent of Code. I started
this series with two intents: to demonstrate an approach to solving
the problems that is more idiomatic to Common Lisp,
and to learn more about the series
library.
I don't claim my solutions are the best. They could all use
some improvement, and I'm sure you code golfers can find numerous
ways to shave strokes. But I think each solution is fairly reasonable and
tries to show off how to effectively use Common Lisp in a number of
simple prolems.
For these problems I purposefully avoided the loop
macro and tried to use the series
library as much as
possible. I used named-let
for the more complex
iterations.
I was ultimately disappointed in series
. I like the
idea of automatically generating pipelines from a more functional
style, but it simply hits the complexity wall far too quickly. For
simple iterations, it's great, but for anything even slightly more
complex, it becomes difficult to use.
The full source code I wrote is available on GitHub
at https://github.com/jrm-code-project/Advent2024
Be aware that I have not included the puzzle input files.
The code will not run without them. You can download the puzzle inputs
from the Advent of Code website and put them in the appropriate
directories, each in a file called input.txt
I'm curious to hear what you think of my solutions. If you have any comments or suggestions, please feel free to contact me via email or by leaving a comment.
1 comment:
Thanks. It is so delightful to watch somebody who uses Common Lisp fluently. I occasionally use series since it always makes me smile. And I often hack the reader so that reading a file gives me something I can hand off to eval or a hack over the pretty printer. I often wrap that form it reads in a macrolet.
Post a Comment