If you have a lisp installation, emacs, org-mode, and org-babel support for lisp installed you can run this by:
- Starting slime (
M-x slime
) - Typing
C-c C-c
in the block initialize. - In the repl type
(in-package :aoc-2018-20)
- Typing
C-c C-c
in the block answers
(unless (find-package :cl-ppcre)
(ql:quickload "cl-ppcre"))
(unless (find-package :iterate)
(ql:quickload "iterate"))
(unless (find-package :parseq)
(ql:quickload "parseq"))
(unless (find-package :fiveam)
(ql:quickload "fiveam"))
<<packages>>
(defpackage :aoc-2018-20
(:use :common-lisp
:iterate
:parseq
:fiveam)
(:export :problem-a
:problem-b))
(in-package :aoc-2018-20)
This is an interesting problem. The input is given as a regular expression style description of the directions through a complex. I’m going to make use of parseq to separate this into a tree structure.
(defun direction (d)
(case d
(#\N #C(0 -1))
(#\S #C(0 1))
(#\E #C(1 0))
(#\W #C(-1 0))
(otherwise #C(0 0))))
(defun parse-alternates (s result alt)
(let ((c (read-char s)))
(case c
(#\) (push (reverse alt) result)
(reverse result))
(#\( (push (parse-alternates s nil nil) alt)
(parse-alternates s result alt))
(#\| (push (reverse alt) result)
(parse-alternates s result nil))
(otherwise (push (direction c) alt)
(parse-alternates s result alt)))))
(defun parse (s result)
(let ((c (read-char s)))
(case c
(#\$ (reverse result))
(#\( (push (parse-alternates s nil nil) result)
(parse s result))
(otherwise (push (direction c) result)
(parse s result)))))
(defun parse-input (directions)
(with-input-from-string (s directions)
(read-char s)
(parse s nil)))
(defun read-input (file)
(iter (for line in-file file using #'read-line)
(collect line)))
(defparameter *input*
(read-input "input/20.txt"))
(defun build-map (directions)
(let ((map (make-hash-table)))
(labels ((recurse (directions position)
(let ((next (car directions)))
(cond ((null directions) nil)
((numberp next)
(pushnew position (gethash (+ position next) map))
(pushnew (+ position next) (gethash position map))
(recurse (cdr directions) (+ position next)))
((listp next)
(iter (for alt in next)
(recurse alt position))
(recurse (cdr directions) position))))))
;; (recurse (append alt (cdr directions)) position)))))))
(recurse directions 0)
map)))
(defun bounds (map)
(let ((max-x most-negative-double-float)
(max-y most-negative-double-float)
(min-x most-positive-double-float)
(min-y most-positive-double-float))
(iter (for (k v) in-hashtable map)
(setf max-x (max max-x (realpart k)))
(setf max-y (max max-y (imagpart k)))
(setf min-x (min min-x (realpart k)))
(setf min-y (min min-y (imagpart k))))
(list max-x max-y min-x min-y)))
(defun print-map (map)
(destructuring-bind (maxx maxy minx miny) (bounds map)
(format t "~a~%" (make-string (+ 3 (* 2 (- maxx minx))) :initial-element #\#))
(iter (for y from miny to maxy)
(format t "#")
(iter (for x from minx to maxx)
(if (= 0 (complex x y))
(format t "X")
(format t "."))
(if (member (complex (1+ x) y) (gethash (complex x y) map))
(format t "|")
(format t "#")))
(format t "~%#")
(iter (for x from minx to maxx)
(if (member (complex x (1+ y)) (gethash (complex x y) map))
(format t "-#")
(format t "##")))
(format t "~%"))))
(defun max-doors (map &optional (position 0))
(let ((search (make-hash-table))
(max most-negative-double-float)
(current nil)
(next (list 0)))
(setf (gethash position search) 0)
(iter (while next)
(setf current next)
(setf next nil)
(iter (for p in current)
(iter (for n in (gethash p map))
(unless (gethash n search)
(setf (gethash n search) (1+ (gethash p search)))
(setf max (max (gethash n search) max))
(pushnew n next)))))
max))
(defun problem-a () (format t "Problem 20 A: ~a~%" (max-doors (build-map (parse-input (car *input*))))))
Now we want the number of rooms with a shortest path 1000 or longer.
(defun count-doors (map limit &optional (position 0))
(let ((search (make-hash-table))
(max most-negative-double-float)
(current nil)
(next (list 0)))
(setf (gethash position search) 0)
(iter (while next)
(setf current next)
(setf next nil)
(iter (for p in current)
(iter (for n in (gethash p map))
(unless (gethash n search)
(setf (gethash n search) (1+ (gethash p search)))
(setf max (max (gethash n search) max))
(pushnew n next)))))
(iter (for (k v) in-hashtable search)
(count (<= limit v)))))
(defun problem-b () (format t "Problem 20 B: ~a~%" (count-doors (build-map (parse-input (car *input*))) 1000)))
<<parse-input>>
<<build-map>>
<<print-map>>
<<max-doors>>
<<count-doors>>
<<read-input>>
<<initialize>>
<<structs>>
<<functions>>
<<input>>
<<problem-a>>
<<problem-b>>
(problem-a)
(problem-b)
Problem 20 A: 4018 Problem 20 B: 8581
(def-suite aoc.2018.20)
(in-suite aoc.2018.20)
(test distance-test
(is (= 31 (max-doors (build-map (parse-input "^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$")))))
(is (= 23 (max-doors (build-map (parse-input "^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$")))))
(is (= 18 (max-doors (build-map (parse-input "^ENNWSWW(NEWS|)SSSEEN(WNSE|)EE(SWEN|)NNN$")))))
(is (= 10 (max-doors (build-map (parse-input "^ENWWW(NEEE|SSE(EE|N))$")))))
(is (= 3 (max-doors (build-map (parse-input "^WNE$"))))))
(run! 'aoc.2018.20)
Running test suite AOC.2018.20 Running test DISTANCE-TEST ..... Did 5 checks. Pass: 5 (100%) Skip: 0 ( 0%) Fail: 0 ( 0%)