#+title: Solution to p21 Load map #+begin_src emacs-lisp :results none (require 'dash) (with-temp-buffer (insert-file-contents "input-test") (advent/replace-multiple-regex-buffer '(("^\\(.*\\)$" . "\"\\1\""))) (goto-char (point-min)) (insert "(setq data '(") (goto-char (point-max)) (insert "))") (eval-buffer)) (setq data-chars (-map #'advent/split-string-into-char-list data) height (length data-chars) width (length (car data-chars))) #+end_src #+begin_src emacs-lisp :results none (setq numeric-keypad '("789" "456" "123" " 0A")) (setq dir-keypad '(" ^A" "")) (setq numeric-keypad-data (-map #'advent/split-string-into-char-list numeric-keypad) dir-keypad-data (-map #'advent/split-string-into-char-list dir-keypad)) #+end_src This function takes a string and returns a list of ~(a . b)~, where these are the adjacent pairs in the string. For instance, if you feed it with "ABC" it will return ((?A . ?B) (?B . ?C)) #+begin_src emacs-lisp :results none (defun split-into-char-pairs (s) (let ((char-list (advent/split-string-into-char-list s))) (-zip-pair (-drop-last 1 char-list) (-drop 1 char-list)))) (defun char-pairs-to-coordinate-pairs (p keypad) (cons (car (advent/coordinates-of (car p) keypad)) (car (advent/coordinates-of (cdr p) keypad)))) #+end_src This takes a cons cell (p . q) of positions and returns all paths that go from p to q without getting out of the boundary #+begin_src emacs-lisp (defun subtract-vector (a b) (-zip-with #'- a b)) (defun add-vector (a b) (-zip-with #'+ a b)) (defun permute-string (s) "Return a list with all strings that can be obtained by permuting characters of S" (--map (apply #'string it) (-permutations (advent/split-string-into-char-list s)))) (defun connecting-paths (r keypad) (--map (concat it "A") (--filter (path-allowed-p it (car r) keypad) (let* ((difference (subtract-vector (cdr r) (car r))) (hor (car difference)) (ver (cadr difference))) (permute-string (concat (if (< hor 0) (make-string (- 0 hor) ?<) (make-string hor ?>)) (if (< ver 0) (make-string (- 0 ver) ?^) (make-string ver ?v)))))))) (setq dir-alist '((?> .(1 0)) (?< . (-1 0)) (?^ . (0 -1)) (?v . (0 1)))) (defun path-allowed-p (s start keypad) (not (--any (eq it 32) (--map (advent/char-at it keypad) (-reductions-from #'add-vector start (--map (cdr (assoc it dir-alist)) (advent/split-string-into-char-list s))))))) #+end_src #+RESULTS: : path-allowed-p The following of course only works for part 1; with part 2 the stack would blow in my face since we would have too many options to check. #+begin_src emacs-lisp (defun directions-for-string (s keypad-data) (--map (connecting-paths it keypad-data) (--map (char-pairs-to-coordinate-pairs it keypad-data) (split-into-char-pairs (concat "A" s))))) (defun pick-shortest-string (l) "picks the shortest string in the list L" (-reduce (lambda (a b) (if (> (length a) (length b)) b a)) l)) (defun shortest-path (s) (let* ((first-directional-keypad (directions-for-string s numeric-keypad-data)) (second-directional-keypad (--map ; this iterates on the possible sequences of the num (--map (directions-for-string it dir-keypad-data) it) first-directional-keypad)) (third-directional-keypad (--map ; this iterates on the possible sequences of the num (--map ; this iterates on the possible sequences for the dir #1 (--map ; this iterates on the possible sequences for the dir #2 (--map (directions-for-string it dir-keypad-data) it) it) it) second-directional-keypad) )) (let* ((level-two (--map (--map (--map (--map (apply #'concat (-map #'pick-shortest-string it)) it) it) it) third-directional-keypad)) (level-one (--map (--map (apply #'concat (-map #'pick-shortest-string it)) it) level-two))) (apply #'concat (-map #'pick-shortest-string level-one))))) (defun replace-in-string (what with in) (replace-regexp-in-string (regexp-quote what) with in nil 'literal)) (defun complexity (s) (* (string-to-number (replace-in-string "A" "" s)) (length (shortest-path s)))) (-reduce #'+ (-map #'complexity data)) #+end_src #+RESULTS: : 1972 The new idea would be to run things bottom-down. For a given pair of chars at a given depth, what is the optimal way to put things together? It should be true that the optimal way to assemble things is the shortest concatenation of the optimal way at the previous depth So we start from all possible combinations of keypad pairs #+begin_src emacs-lisp (setq direction-pairs (let ((directions (advent/split-string-into-char-list "<>^vA"))) (-mapcat (lambda (x) (--map (cons x it) directions)) directions))) (setq basis-induction (--map (cons it (connecting-paths (char-pairs-to-coordinate-pairs it dir-keypad-data) dir-keypad-data)) direction-pairs) first-step (--map (cons (car it) (length (pick-shortest-string (cdr it)))) basis-induction) ) ; here first-step contains the keys that need to be pressed on the ; human keypad to have the first keypad do as described (defun iterate-robot (s al) (apply #'+ (--map (cdr (assoc it al)) (split-into-char-pairs (concat "A" s))))) (defun iterate-step (l) (--map (cons (car it) (-min (--map (iterate-robot it l) (cdr it)))) basis-induction)) ; here iterate-step takes a list at step n and returns the same list ; at step n+1 (setq last-step (funcall (-iteratefn 'iterate-step 24) first-step)) (defun shortest-stuff (s) (apply '+ (--map (-min (--map (iterate-robot it last-step) it)) (directions-for-string s numeric-keypad-data)))) (defun complexity (s) (* (string-to-number (replace-in-string "A" "" s)) (shortest-stuff s))) (-reduce #'+ (-map #'complexity data)) ;oh, it does… #+end_src #+RESULTS: : 271397390297138