Commit Diff


commit - 3311bc2c2ab44d0aa19ca977e2e2715ae6969f2f
commit + f771a9652753fd89882480d30984e2378b32e7c8
blob - /dev/null
blob + bf246da8f2f59db5ed2db06af783aa2898bbc080 (mode 644)
--- /dev/null
+++ input/21.txt
@@ -0,0 +1,5 @@
+780A
+846A
+965A
+386A
+638A
blob - /dev/null
blob + 87d985d55c715331cc4eb8512c722518a6666a79 (mode 644)
--- /dev/null
+++ src/day-21.lisp
@@ -0,0 +1,103 @@
+(defpackage #:aoc/day-21
+  (:use #:cl #:aoc/utils)
+  (:export #:day-21))
+(in-package #:aoc/day-21)
+
+(defparameter *keypad*
+  (with-input-from-string (s "789
+456
+123
+#0A")
+    (make-map s)))
+
+(defparameter *directional-pad*
+  (with-input-from-string (s "#^A
+<v>")
+    (make-map s)))
+
+(defparameter *directions* '((0 . -1) (1 . 0) (0 . 1) (-1 . 0)))
+
+(defun direction (dir)
+  (eswitch (dir :test #'equal)
+    ('(1 . 0) #\>)
+    ('(-1 . 0) #\<)
+    ('(0 . 1) #\v)
+    ('(0 . -1) #\^)))
+
+(defun dfs (map start end width height max)
+  (loop with stack = (list (list start nil 0))
+        with possible-paths = nil
+        for (pos dirs length) = (pop stack)
+        when (null pos)
+          do (return possible-paths)
+        when (equal pos end)
+          do (push (reverse dirs) possible-paths)
+        when (< length max)
+          do (loop for dir in *directions*
+                   for next = (point+ pos dir)
+                   when (and (point-in-bounds-p next width height)
+                             (char/= (map-cell map next) #\#))
+                     do (push (list next (cons (direction dir) dirs) (1+ length)) stack))))
+
+(defun build-arm-movements-cache (map)
+  (loop with cache = (make-hash-table :test #'equal)
+        with width = (input-map-width map)
+        with height = (input-map-height map)
+        for y-1 from 0 below height
+        do (loop for x-1 from 0 below width
+                 for p-1 = (cons x-1 y-1)
+                 for c-1 = (map-cell map p-1)
+                 unless (char= c-1 #\#)
+                   do (loop for y-2 from 0 below height
+                            do (loop for x-2 from 0 below width
+                                     for p-2 = (cons x-2 y-2)
+                                     for c-2 = (map-cell map p-2)
+                                     for max-distance = (manhattan-distance p-1 p-2)
+                                     unless (or (char= c-2 #\#)
+                                                (char= c-1 c-2))
+                                       do (setf (gethash (cons c-1 c-2) cache)
+                                                (dfs map p-1 p-2 width height max-distance)))))
+        finally (return cache)))
+
+(defparameter *keypad-movements* (build-arm-movements-cache *keypad*))
+(defparameter *directional-pad-movements* (build-arm-movements-cache *directional-pad*))
+
+(defun all-possibilities (buttons cache)
+  (loop with all-possibilities = nil
+        with last = #\A
+        for button in buttons
+        for possibilities = (gethash (cons last button) cache)
+        if possibilities
+          do (push (mapcar (rcurry #'append '(#\A)) possibilities) all-possibilities)
+        else
+          do (push '((#\A)) all-possibilities)
+        do (setf last button)
+        finally (return (nreverse all-possibilities))))
+
+(defun make-robot (next movements-map)
+  (let ((cache (make-hash-table :test #'equal)))
+    (lambda (sequence)
+      (loop for possibilities in (all-possibilities sequence movements-map)
+            sum (loop for possibility in possibilities
+                      minimize (or (gethash possibility cache)
+                                   (setf (gethash possibility cache)
+                                         (funcall next possibility))))))))
+
+(defun make-robots (n-robots)
+  (loop with current = (make-robot #'length *directional-pad-movements*)
+        for i from 1 to n-robots
+        do (setf current (make-robot current (if (= i n-robots)
+                                                 *keypad-movements*
+                                                 *directional-pad-movements*)))
+        finally (return current)))
+
+(defun day-21 (input)
+  (loop with task-1-robots = (make-robots 2)
+        with task-2-robots = (make-robots 25)
+        for line = (read-line input nil)
+        until (null line)
+        for buttons = (coerce line 'list)
+        for numeric-value = (parse-integer line :junk-allowed t)
+        sum (* (funcall task-1-robots buttons) numeric-value) into task-1
+        sum (* (funcall task-2-robots buttons) numeric-value) into task-2
+        finally (return (values task-1 task-2))))
blob - /dev/null
blob + eab7567f536cda50b370c82687bc604b175989c0 (mode 644)
--- /dev/null
+++ t/day-21.lisp
@@ -0,0 +1,12 @@
+(defpackage #:aoc-test/day-21
+  (:use #:cl #:lisp-unit2)
+  (:import-from #:aoc/day-21))
+(in-package #:aoc-test/day-21)
+
+(define-test test-day-21
+    ()
+  (assert= 126384 (aoc:run-day 21 "029A
+980A
+179A
+456A
+379A")))