aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan Marín Noguera <juan@mnpi.eu>2024-12-21 13:31:00 +0100
committerJuan Marín Noguera <juan@mnpi.eu>2024-12-21 13:31:00 +0100
commita6d469629ff1d16d6e65512c45b610e21e1e9d58 (patch)
tree0d2a250b8a7333ddd42a781be43e1e73f5240a30
parentc043dabee03b4f5febc445f13045887fd57dd94a (diff)
Day 20
-rw-r--r--day20.scm110
1 files changed, 110 insertions, 0 deletions
diff --git a/day20.scm b/day20.scm
new file mode 100644
index 0000000..ea4225a
--- /dev/null
+++ b/day20.scm
@@ -0,0 +1,110 @@
+(define-module (day20)
+ #:use-module (ice-9 rdelim)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-2)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-69))
+
+(define-record-type <race> (make-race map start end) race?
+ (map race-map)
+ (start race-start)
+ (end race-end))
+
+(define* (read-race #:optional (input (current-input-port)))
+ (define start #f)
+ (define end #f)
+ (define (check-char c i j)
+ (case c
+ ((#\S)
+ (when start (error "Two start positions in the map."))
+ (set! start (cons i j)))
+ ((#\E)
+ (when end (error "Two end positions in the map."))
+ (set! end (cons i j)))
+ ((#\. #\#) #f)
+ (else (error "Unexpected character in the map." c))))
+ (define (iter acc i)
+ (define line (read-line input))
+ (if (or (eof-object? line) (string=? "" line))
+ (list->typed-array 'b 2 (reverse acc))
+ (begin (string-for-each-index
+ (lambda (j) (check-char (string-ref line j) i j)) line)
+ (iter (cons (map (cut char=? #\# <>) (string->list line)) acc)
+ (+ i 1)))))
+ (define mp (iter '() 0))
+ (unless start (error "Map doesn't have a start character."))
+ (unless end (error "Map doesn't have an end character."))
+ (make-race mp start end))
+
+(define (pair+ a b)
+ (cons (+ (car a) (car b)) (+ (cdr a) (cdr b))))
+
+(define (race->hash-table race)
+ (define ht (make-hash-table))
+ (define (possible-next? pos)
+ (cond ((hash-table-exists? ht pos) #f)
+ ((not (array-in-bounds? (race-map race) (car pos) (cdr pos)))
+ (error "The race goes outside the map." pos))
+ (else (not (array-ref (race-map race) (car pos) (cdr pos))))))
+ (define (iter p pos)
+ (hash-table-set! ht pos p)
+ (unless (equal? pos (race-end race))
+ (let ((next (filter possible-next?
+ (map (cut pair+ pos <>)
+ '((1 . 0) (0 . 1) (-1 . 0) (0 . -1))))))
+ (if (= 1 (length next))
+ (iter (+ p 1) (car next))
+ (error "There must be exactly one way to continue the race."
+ pos next)))))
+ (iter 0 (race-start race))
+ ht)
+
+(define-record-type <cheat> (make-cheat start end savings) cheat?
+ (start cheat-start)
+ (end cheat-end)
+ (savings cheat-savings))
+
+(define (list-diffs max-distance)
+ (let iter-row ((i max-distance) (acc '()))
+ (if (< i 0)
+ acc
+ (let ((max-col (- max-distance i)))
+ (let iter-col ((j (- max-col)) (acc acc))
+ (if (> j max-col)
+ (iter-row (- i 1) acc)
+ (iter-col (+ j 1)
+ (cons (cons i j)
+ (if (zero? i)
+ acc
+ (cons (cons (- i) j) acc))))))))))
+
+(define *cheat-diffs* (make-parameter (list-diffs 2)))
+
+(define (cheats ht start start-index)
+ (define (check-crossing diff)
+ (define end (pair+ start diff))
+ (and-let* ((end-index (hash-table-ref/default ht end #f))
+ (points (- end-index
+ start-index (abs (car diff)) (abs (cdr diff))))
+ ((positive? points)))
+ (make-cheat start end points)))
+ (filter-map check-crossing (*cheat-diffs*)))
+
+(define *saving-threshold* (make-parameter 100))
+
+(define (count-good-cheats ht start start-index)
+ (length (filter! (lambda (c) (>= (cheat-savings c) (*saving-threshold*)))
+ (cheats ht start start-index))))
+
+(define (race-good-cheats race)
+ (define ht (race->hash-table race))
+ (hash-table-fold ht
+ (lambda (k v prev) (+ prev (count-good-cheats ht k v))) 0))
+
+(define* (part1 #:optional (port (current-input-port)))
+ (race-good-cheats (read-race port)))
+
+(define* (part2 #:optional (port (current-input-port)))
+ (parameterize ((*cheat-diffs* (list-diffs 20)))
+ (race-good-cheats (read-race port))))