aboutsummaryrefslogtreecommitdiffstats
path: root/AoC2022/12/solver.lisp
blob: e00861a5b94ab553031e20deea60bd81caf06b45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
(ql:quickload '(fiveam uiop))

(defun possible-directions (width height)
  ;; point on grid is p=x+y*width
  (lambda (pos)
    (let ((x (mod pos width))
          (y (floor pos width)))
      (delete nil
              (list
               (when (< -1 x  (1- width)) (1+ pos))      ;; right move
               (when (< -1 y (1- height)) (+ pos width)) ;; down move
               (when (<  0 x width) (1- pos))            ;; left move
               (when (<  0 y height) (- pos width)))     ;; up move
              :test #'eq))))

(defun elevations (data)
  (map 'vector (lambda (chr)
                    (case chr
                      (#\S 0)
                      (#\E 27)
                      (t (- (char-code chr) 96))))
       data))

(defun next-steps (place candidates land paths)
  (let ((elevation (aref land place)))
    (unless (= 27 elevation)
      (mapcan (lambda (option)
                (when (and (not (gethash option paths))
                           (>= (1+ elevation) (aref land option)))
                  (setf (gethash option paths) (cons option (gethash place paths)))
                  (list option)))
              (funcall candidates place)))))

(defun shortest-path (starts candidates land paths)
  (let ((next (mapcan (lambda (place) (next-steps place candidates land paths)) starts)))
    (when next
      (shortest-path next candidates land paths))))

(defun solver (input-file start-p)
  (let* ((data (uiop:read-file-lines input-file))
         (candidates (possible-directions (length (car data)) (length data)))
         (land (elevations (apply #'concatenate 'string data)))
         (paths (make-hash-table :test #'eq))
         (finish (position 27 land :test #'eq))
         (starts (loop for i from 0
                       for el across land
                       when (funcall start-p el) collect (progn (setf (gethash i paths) (list i)) i))))
    (shortest-path starts candidates land paths)
    (1- (length (gethash finish paths)))))

(fiveam:test solver
  (fiveam:is (= 31 (solver "eg-in" (lambda (x) (= x 0)))))
  (fiveam:is (= 29 (solver "eg-in" (lambda (x) (<= x 1)))))
  (fiveam:is (= 361 (solver "input" (lambda (x) (= x 0)))))
  (fiveam:is (= 354 (solver "input" (lambda (x) (<= x 1))))))

(fiveam:run-all-tests)