Day 20: Part 2.
This commit is contained in:
parent
6b69e33b5c
commit
42b3694c0a
94
src/20.rkt
94
src/20.rkt
|
@ -1,6 +1,8 @@
|
|||
#lang racket
|
||||
|
||||
(require graph
|
||||
data/heap
|
||||
racket/set
|
||||
(except-in "../lib.rkt" transpose))
|
||||
|
||||
(define input
|
||||
|
@ -17,10 +19,18 @@
|
|||
(define vector-grid
|
||||
(lists->vectors list-grid))
|
||||
|
||||
;; get-char : coord -> char
|
||||
;; coord = (list number number)
|
||||
;; Get the character at the specified coordinate
|
||||
;; where x increases rightwards and y increases downwards.
|
||||
(define (get-char coord)
|
||||
(match-let ([(list x y) coord])
|
||||
(vector-ref (vector-ref vector-grid y) x)))
|
||||
|
||||
;; neighbours : coord -> (listof coord)
|
||||
;; If the coordinate is an open passage,
|
||||
;; get the passages accessible from it;
|
||||
;; otherwise, return an empty list.
|
||||
(define (neighbours coord)
|
||||
(if (nchar=? #\. (get-char coord)) '()
|
||||
(match-let* ([(list x y) coord]
|
||||
|
@ -30,6 +40,9 @@
|
|||
(list x (sub1 y)))])
|
||||
(filter (∘ (∂ char=? #\.) (∂ get-char)) ncoords))))
|
||||
|
||||
;; graph-grid : unweighted, undirected graph
|
||||
;; Vertices: coords
|
||||
;; Edges: between accessible passages
|
||||
(define graph-grid
|
||||
(let ([graph (unweighted-graph/undirected '())]
|
||||
[coords (cartesian-product (range 2 (- width 2))
|
||||
|
@ -39,7 +52,17 @@
|
|||
(add-edge! graph coord ncoord)))
|
||||
graph))
|
||||
|
||||
(define (add-coord-pairs hash coord-pairs side)
|
||||
;; add-coord-pairs! : (hashof (symbol => coord)) -> (listof (list coord coord))
|
||||
;; -> ('top | 'bottom | 'left | 'right)
|
||||
;; -> void
|
||||
;; The coordinate pairs represent the stripes of points bordering the maze.
|
||||
;; For instance, the top stripe would be the list (((0 0) (0 1)) (((1 0) (1 1)) ...)
|
||||
;; We check whether there are two letters at those two points, which represent a portal.
|
||||
;; Then we add the portal into the hash from the symbol of its name to its coordinate.
|
||||
;; The side parameter tells us if we're looking at a stripe that borders the maze on the
|
||||
;; top, bottom, left, or right. Notice that for the inner portals, the "top" stripe
|
||||
;; (i.e. with smallest y coordinates) is a 'bottom stripe, since it sits below a row of maze.
|
||||
(define (add-coord-pairs! hash coord-pairs side)
|
||||
(for ([coord-pair coord-pairs])
|
||||
(match-let* ([(list c1 c2) coord-pair]
|
||||
[char1 (get-char c1)]
|
||||
|
@ -58,6 +81,15 @@
|
|||
(string->symbol (list->string (list char1 char2)))
|
||||
coord)))))
|
||||
|
||||
;; make-portals : (list number number) x 4 -> (hashof (symbol => coord))
|
||||
;; For each argument, the first number in the list represents the coordinate
|
||||
;; of the first letter of the portals in that stripe, and idem for the second.
|
||||
;; The coordinate given depends on whether the stripe is vertical or horizontal.
|
||||
;; For instance, for the outer portals, the top stripe runs horizontally,
|
||||
;; so x varies over all possible points, while y = 0, 1.
|
||||
;; For the right stripe, which runs vertically, x = width - 2, width - 1.
|
||||
;; The order of the coordinates gives the order of the letters of the name of the portal.
|
||||
;; This returns a hashmap from portal symbol names to their coordinates.
|
||||
(define (make-portals top-coords bottom-coords left-coords right-coords)
|
||||
(let ([hash (make-hash)]
|
||||
[top-coord-pairs
|
||||
|
@ -76,24 +108,32 @@
|
|||
(map list
|
||||
(cartesian-product (list (first right-coords)) (range 0 height))
|
||||
(cartesian-product (list (second right-coords)) (range 0 height)))])
|
||||
(add-coord-pairs hash top-coord-pairs 'top)
|
||||
(add-coord-pairs hash bottom-coord-pairs 'bottom)
|
||||
(add-coord-pairs hash left-coord-pairs 'left)
|
||||
(add-coord-pairs hash right-coord-pairs 'right)
|
||||
(add-coord-pairs! hash top-coord-pairs 'top)
|
||||
(add-coord-pairs! hash bottom-coord-pairs 'bottom)
|
||||
(add-coord-pairs! hash left-coord-pairs 'left)
|
||||
(add-coord-pairs! hash right-coord-pairs 'right)
|
||||
hash))
|
||||
|
||||
;; outer-portals : (hashof (symbol => coord))
|
||||
;; A list of the portals on the outer edge of the maze.
|
||||
;; Note that this also includes the start and end points AA, ZZ,
|
||||
;; so it should have two more entries than inner-portals.
|
||||
(define outer-portals
|
||||
(make-portals (list 0 1)
|
||||
(list (- height 2) (- height 1))
|
||||
(list 0 1)
|
||||
(list (- width 2) (- width 1))))
|
||||
|
||||
;; inner-portals : (hashof (symbol => coord))
|
||||
;; A list of the portals on the inner edge of the maze.
|
||||
(define inner-portals
|
||||
(make-portals (list 90 91)
|
||||
(list 37 38)
|
||||
(list 96 97)
|
||||
(list 37 38)))
|
||||
|
||||
;; Since we can travel between portals of the same name,
|
||||
;; we add an edge between them in the graph.
|
||||
(for ([portal (hash-keys inner-portals)])
|
||||
(add-edge! graph-grid (hash-ref outer-portals portal) (hash-ref inner-portals portal)))
|
||||
|
||||
|
@ -102,4 +142,46 @@
|
|||
(hash-ref outer-portals 'AA)
|
||||
(hash-ref outer-portals 'ZZ)))))
|
||||
|
||||
(show-solution part1 #f)
|
||||
;; This is a BFS of the graph, taking into account the level.
|
||||
;; Heap elements are (list coord number (setof (list coord number)) number)
|
||||
;; or, giving them names, (list coord level visited count), where
|
||||
;; - coord is the current position
|
||||
;; - level is the current floor level
|
||||
;; - visited is a set of visited locations consisting of a position and a level
|
||||
;; - count is the number of steps we have taken since AA
|
||||
;; When visiting a position + level, we consider its neighbours to visit next.
|
||||
;; If we are travelling from an inner portal to an outer portal, we are going
|
||||
;; one level deeper, so we increment the level, and vice versa for outer to inner.
|
||||
;; We do not allow negative levels, and we do not revisit locations.
|
||||
;; We also do not visit any level deeper than 25; this was found through trial and error.
|
||||
;; If we are at ZZ on level 0, we're done, and we return the number of steps taken.
|
||||
;; If we run out of locations to visit (e.g. by restricting the deepest level to < 25),
|
||||
;; we will get an empty-heap error.
|
||||
(define part2
|
||||
(let* ([inner-portal-coords (list->set (hash-values inner-portals))]
|
||||
[outer-portal-coords (list->set (hash-values outer-portals))]
|
||||
[heap (make-heap (λ (v1 v2) (< (fourth v1) (fourth v2))))]
|
||||
[AA (hash-ref outer-portals 'AA)]
|
||||
[ZZ (hash-ref outer-portals 'ZZ)])
|
||||
(heap-add! heap (list AA 0 (set (cons AA 0)) 0))
|
||||
(match-let loop ([(list coord level visited count) (heap-min heap)])
|
||||
(heap-remove-min! heap)
|
||||
(if (and (= level 0) (equal? coord ZZ))
|
||||
count
|
||||
(begin
|
||||
(for ([ncoord (get-neighbors graph-grid coord)])
|
||||
(let* ([level
|
||||
(cond
|
||||
[(and (set-member? inner-portal-coords coord)
|
||||
(set-member? outer-portal-coords ncoord)) (add1 level)]
|
||||
[(and (set-member? outer-portal-coords coord)
|
||||
(set-member? inner-portal-coords ncoord)) (sub1 level)]
|
||||
[else level])])
|
||||
(unless (or (< level 0) (> level 25) (set-member? visited (cons ncoord level)))
|
||||
(heap-add! heap (list ncoord
|
||||
level
|
||||
(set-add visited (cons ncoord level))
|
||||
(add1 count))))))
|
||||
(loop (heap-min heap)))))))
|
||||
|
||||
(show-solution part1 part2)
|
Loading…
Reference in New Issue