I have recently started learning lisp by reading ANSI Common Lisp, and I thought advent of code would be good for practice. Any feedback on my code for Day 1 would be appreciated.
(require :uiop)
(defparameter *input-file* "01-input")
(defparameter *input-lines* (uiop:read-file-lines *input-file*))
(defun solve ()
(multiple-value-bind (xs ys) (parse-input-lines *input-lines*)
(format t "Part 1: ~a~%" (part-1 xs ys))
(format t "Part 2: ~a~%" (part-2 xs ys))))
(defun parse-input-lines (lines)
(let (xs ys)
(loop
for line in lines
do (with-input-from-string (s line)
(push (read s) xs)
(push (read s) ys)))
(values (sort xs #'<) (sort ys #'<))))
(defun part-1 (xs ys)
(reduce #'+ (mapcar (lambda (x y) (abs (- x y))) xs ys)))
(defun part-2 (xs ys)
(let ((counts (make-hash-table)))
(loop for y in ys
do (setf (gethash y counts) (1+ (or (gethash y counts) 0))))
(loop for x in xs
sum (* x (or (gethash x counts) 0)) into similarity-score
finally (return similarity-score))))
1 Answer 1
Background: Input data is a series of lines, where each line has two integers separated by multiple spaces.
(loop
for line in lines
do (with-input-from-string (s line)
(push (read s) xs)
(push (read s) ys)))
Not an issue if you're just using the problem's input, but read
on untrusted input data can cause arbitrary code execution via reader macros. Safer to disable them (And ensure other sane defaults) with
(uiop:with-safe-io-syntax ()
(loop
for line in lines
do (with-input-from-string (s line)
(push (read s) xs)
(push (read s) ys))))
I'd take advantage of loop
's collect ... into ...
form and parse-integer
, though, and skip string streams and read
completely:
(defun parse-input-lines (lines)
(loop
for line in lines
for first-space = (position #\Space line)
collect (parse-integer line :end first-space) into xs
collect (parse-integer line :start first-space) into ys
finally (return (values (sort xs #'<) (sort ys #'<)))))
(Well, my actual Common Lisp solution for this problem used cl-ppcre
regular expressions to parse the lines.)
(loop for y in ys
do (setf (gethash y counts) (1+ (or (gethash y counts) 0))))
gethash
takes an optional third argument that's used as the default value if the given key isn't present in the hash table, so this could be simplified to
(loop for y in ys
do (setf (gethash y counts) (1+ (gethash y counts 0))))
but it's even better using incf
:
(loop for y in ys
do (incf (gethash y counts 0)))
*input-file*
, with earmuffs, looks great. But the*input-lines*
global feels unnatural. Prefer to just leave it as an anonymous expression within thatmultiple-value-bind
. \$\endgroup\$