aoc-cl/src/2024/day18.lisp
2024-12-18 08:28:34 +01:00

212 lines
4.7 KiB
Common Lisp

(defpackage :aoc/2024/18
(:use :cl :aoc :alexandria :trivia :lla :queues)
(:export
#:sample-data
#:sample-data2
#:part1
#:part2
#:in-bounds-p
#:make-pos
#:pos-x
#:pos-y
#:next-moves
))
(in-package :aoc/2024/18)
(defstruct pos
(x 0 :type fixnum)
(y 0 :type fixnum))
(defun pos-move (pos dir)
(case dir
(#\^ (make-pos :x (pos-x pos) :y (1- (pos-y pos))))
(#\v (make-pos :x (pos-x pos) :y (1+ (pos-y pos))))
(#\< (make-pos :x (1- (pos-x pos)) :y (pos-y pos)))
(#\> (make-pos :x (1+ (pos-x pos)) :y (pos-y pos)))
))
(defun show-map (map &optional (dest t))
(let ((w (array-dimension map 1))
(h (array-dimension map 0)))
(format
dest "~{~A~}"
(loop
for row from 0 below h
collect (format nil "~A~%"
(coerce
(loop for col from 0 below w
collect (aref map row col))
'string))))))
(defun get-pos (map pos)
(aref map (pos-y pos) (pos-x pos)))
(defun set-pos (map pos tile)
(setf (aref map (pos-y pos) (pos-x pos)) tile))
(defun parse-line (line)
(let ((p (mapcar #'parse-integer (ppcre:split "," line))))
(make-pos :x (first p) :y (second p))))
(defun parse-input (lines)
(let ((mem (make-hash-table :test #'equalp)))
(loop
for p in (mapcar #'parse-line lines)
for i from 1
do (setf (gethash p mem) i))
mem))
(defstruct memory size map)
(defparameter input-text (test-input 2024 18))
(defparameter input-data
(make-memory
:size 70
:map (parse-input input-text)))
(defparameter sample-text (aoc:split-lines "5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0"))
(defparameter sample-data
(make-memory
:size 6
:map (parse-input sample-text)))
(defun in-bounds-p (mem pos)
(and
(<= 0 (pos-x pos))
(<= 0 (pos-y pos))
(>= (memory-size mem) (pos-x pos))
(>= (memory-size mem) (pos-y pos))
))
(defun next-moves (mem pos &optional (tm 1024))
"return conses of next pos get there"
(loop for d in '(#\^ #\v #\> #\<)
for p = (pos-move pos d)
if (and
(in-bounds-p mem p)
(< tm (gethash p (memory-map mem) 1000000)))
collect p))
(defun distance-estimate-f (finish)
"estimate the distance for a reindeer from given finish"
(let ((a finish))
(lambda (b)
(+
(abs (- (pos-x a) (pos-x b)))
(abs (- (pos-y a) (pos-y b)))
))))
(defstruct state score path)
(defun compare-states-f (finish)
(let ((dist-est (distance-estimate-f finish)))
(lambda (s1 s2)
(< (+ (state-score s1)
(funcall dist-est (car (state-path s1))))
(+ (state-score s2)
(funcall dist-est (car (state-path s2))))))))
(defun best-path (map time)
(let* ((start (make-pos :x 0 :y 0))
(finish (make-pos :x (memory-size map) :y (memory-size map)))
(state-comparison (compare-states-f finish))
(best (make-hash-table :test #'equalp))
(todo (make-queue :priority-queue :compare state-comparison)))
(qpush todo (make-state
:path (list start)
:score 0))
(loop
for s = (qpop todo)
until (or
(null s)
(equalp (first (state-path s)) finish))
finally (return s)
do (loop
for next in (next-moves map (first (state-path s)) time)
for score = (+ (state-score s) 1)
if (> (gethash next best 1000000000) score)
do (progn
(qpush todo
(make-state
:path (cons next (state-path s))
:score score))
(setf (gethash next best) score)))
)))
(defun solve-part1 (data time)
(let ((path (best-path data time)))
(state-score path))
)
(defun part1 (data &optional (time 1024))
(format nil "~A" (solve-part1 data time)))
(defun pos-at-time (mem tm)
(loop
for k being the hash-keys of (memory-map mem)
for v being the hash-values of (memory-map mem)
if (= v tm)
return k))
(defun find-block-position (mem )
(let* ((delta (ash 1 (ceiling (log (hash-table-count (memory-map mem)) 2))))
(tm 0)
(best-tm 0))
(loop
for x = 1
do (format t "delta: ~A, tm: ~A, best-tm: ~A~%" delta tm best-tm)
until (zerop delta)
if (best-path mem tm)
do (progn
(setf best-tm (max best-tm tm))
(incf tm delta))
else
do (decf tm delta)
do (setf delta (ash delta -1))
finally (return (pos-at-time mem (1+ best-tm))))))
(defun part2 (data)
(let ((first-block-pos (find-block-position data)))
(format nil "~A,~A" (pos-x first-block-pos) (pos-y first-block-pos))))
(defun solve-day ()
(format t "part1: ~A~%" (part1 input-data))
(format t "part2: ~A~%" (part2 input-data)))
(defun submit ()
(let ((p1 (part1 input-data))
(p2 (part2 input-data)))
(if p1 (submit-part1 2024 18 p1))
(if p2 (submit-part2 2024 18 p2))))