From 35437aeb6aa66850d572ca18d3f5577996937384 Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Wed, 25 Dec 2019 21:00:30 -0800 Subject: [PATCH] Day 18: Part 2 complete. --- input/18b1.txt | 41 +++++++++ input/18b2.txt | 41 +++++++++ input/18b3.txt | 41 +++++++++ input/18b4.txt | 41 +++++++++ src/18-helper.rkt | 201 ++++++++++++++++++++++++++++++++++++++++ src/18.rkt | 229 ++++++---------------------------------------- 6 files changed, 393 insertions(+), 201 deletions(-) create mode 100644 input/18b1.txt create mode 100644 input/18b2.txt create mode 100644 input/18b3.txt create mode 100644 input/18b4.txt create mode 100644 src/18-helper.rkt diff --git a/input/18b1.txt b/input/18b1.txt new file mode 100644 index 0000000..322feda --- /dev/null +++ b/input/18b1.txt @@ -0,0 +1,41 @@ +######################################### +#.............#..o..........#...........# +#.#######.#.#.#.#.#########.###.#######.# +#.#...A.#.#.#.#.#.#w....#.#...#..e#.....# +#.#.###.###.#.###.###.#.#.###.#####.###.# +#.#...#.....#...#.#...#.....#.....#...#.# +#.###.#########.#.#.#######.#####.###.#.# +#...#.#...........#.......#.....#.....#.# +###.#.###.#############.#.#############.# +#...#...#.#.....#.....#.#.....#...#.....# +#.#####.#.#.###.#.###.#######.#.#.#.##### +#.....#.#.#.#.#.#...#.#.....#.#.#.#.....# +###.###.#.#.#.#.###.#.#.###.#.#.#.#####.# +#...#...#.#...#.#...#.#.#...#...#.....#.# +#.###.#######.#.#.###.#.###.#.#####.###.# +#...#.....#...#.....#.#...#.#.#...#.#...# +###.#####.#.#####.###.###.#.###.#.#.#.### +#.#.....#...#...#.#...#.#.#.#...#.#.#...# +#.#####.#####.#.###.#.#.#.#.#.###.#.###.# +#.#...#.#.....#.....#...#.#...#...#.....# +#.#.#.#.#.###.#########.#.#####.#######.# +#...#...#...#.#...#...#.#.#...#.......#.# +#.#####.###.#.#.#.#.#####.#.#########.#.# +#.#...#...#.#.#.#...#.....#.........#.#.# +#K#Y#.#.###.#.#.#####.#####.###.#####.#.# +#.#.#.#.#...#.#.#.#...#...#.#.#.#...#.#.# +#.#.#.###.#.###.#.#.#.###.#.#.#.#.#.#.#.# +#.#.#.....#.#...#...#...#...#.#.#.#...#.# +###.#######.#.###.#####.#.###.#.#.####### +#...#.....#.#.#...#.....#.....#.#.......# +#.#####.###.#.#.###.#########.#.#######.# +#.....#.....#d#...#.#...#...#.#.......#.# +#.###.#.#####.###.#.#.#.#.#.#.###.#####.# +#.#...#...#.#.#...#.#.#.#.#...#.#.....#.# +#.#.#####.#.#.#####.#.#.#.#####.#####.#.# +#.#.....#.#.#...#...#.#.#...#.......#.#.# +#.#####V#.#.###.#.###.#.###.#.#####.#.#.# +#.....#.#.....#...#...#...#.#.....#.#...# +#####.#.###########.#######.#####.#.##### +#.....#...........................#....@# +######################################### \ No newline at end of file diff --git a/input/18b2.txt b/input/18b2.txt new file mode 100644 index 0000000..d617e54 --- /dev/null +++ b/input/18b2.txt @@ -0,0 +1,41 @@ +######################################### +#...............#b......#...#.........#.# +###.#########.###.###.###.#.#C#####.#.#.# +#...#.....#...#...#...#...#.#.....#.#k..# +#.###.#####.###.###.###.###.#####.#.###.# +#...#.#...#.#...#.#.....#.#.....#.#.#...# +###.#.#.#.#.#.###.#######.###.#.#.#.##### +#...#...#.#.#.......#.....#...#.#.#.#...# +#.###.###.#P#######.###.#.#.###.#.#.#.#.# +#.#.....#.......#...#...#.#.#...#.#...#.# +#.###############.###.#####.#.###.#####.# +#.......#.......#.#.......#.#...#.#...#.# +#.#####.#.#####.#.#######.#.###.#.#.###.# +#.#.#...#.#.#...#...#.....#.#...#.#.#...# +#.#.#.#.#.#.#.#####.#.###.#D###.#.#.#.### +#...#.#.#...#.....#.#.#.X.#...#.#...#...# +#####.#.###.#####.#.#.###.###.#####.###.# +#.....#.#.#.#...#.#.#...#...#...#...#...# +#.#####.#.#.#.#.#.#.###.#.#####.#.###.### +#.#.....#.#.#.#.#.......#.#...#.....#...# +#.#.#####.#.###.#########.#.#.#########.# +#.#.....#.#.#.....#...#...#.#.........#i# +#.#####.#.#.#.#####.#######.#########.#.# +#.....#...#...#n..#..f..#.Z.#...W...#.#.# +#####.#####.###.#.#####.#.###.#.#####.#.# +#.....#.....#...#.......#.#.#.#......h#.# +#.#J###.#####.###########.#.#.#########.# +#.#.#...#...#.....#...#g..#.#.#...#.....# +#.###.#####.#####.#.#.#.###.#.#.###.###.# +#...#.#.......#.#...#...#.....#.....#...# +#.#.#.###.###.#.#########.#####.#####.### +#.#.#...#...#.#.......#.#.#...#...#.#...# +###.###.###.#.###.###.#.#.###.###.#.###.# +#...#.#.....#...#.#.#...#...#.....#.#...# +#.#.#.#########.#.#.###.###.#####.#.#.### +#.#.......#...#...#...#...#.....#.#.#...# +#.#######.#.#.#####.#.###.#####.#.#U###.# +#...#.....#.#.......#.#...#..v#.#.....#.# +#.#.#######.#####.#####.#####.#.#######.# +#@#.............#.........R...#l........# +######################################### \ No newline at end of file diff --git a/input/18b3.txt b/input/18b3.txt new file mode 100644 index 0000000..0aeb4bf --- /dev/null +++ b/input/18b3.txt @@ -0,0 +1,41 @@ +######################################### +#.............#......r..#.......#......@# +#.###########.#######.###.#.###.#.#####.# +#...#.......#.......#.....#.#.#...#...#.# +###.#.###.#.#######.#.#####.#.#######.#.# +#...#.#...#.#.......#.#.....#.......#.#.# +#.###.#.#.###.#######.#.#######.###.#.#.# +#.#...#.#.#.........#.#...#.....#.....#.# +#.#.###.###.#######.#####.#.#.#########.# +#q#...#.....#.....#.....#.#.#.#...#.....# +#.#######.###.###.#####.#.#.###.#.#.##### +#.#.....#...#.#.#.#...#.#.#.#...#.#.....# +#.#.###.#####.#.#.###.#.#.#.#.###.#####.# +#.#.#.#...#...#.#.#...#...#.#...#.......# +#.#L#.###.#.###.#.#.#######.###.######### +#.#...#.#.#.#...#.#.#...#.......#...#...# +#.###.#.#.#.###.#.#.#.#.#.#######.#.#.#.# +#...#...#...#...#.#.#.#.........#.#.#.#.# +#.#.###.#####.#.#H#.#.#########.#.#.#N#.# +#.#.#...#.....#.#.#.....#...#...#.#...#.# +###.#.###.#.#####.#.#####.#.#.###.#####.# +#...#...#.#...#...#.#.T...#.#.#.F.#.#...# +#.#####.#.###.#.###########.###.###.#.### +#.....#.#.#u..#..z........#....t#.G...#.# +###.#.#.###.###########.#########.#####.# +#...#s#...#.....#.....#.........#.#.....# +#.###.###.###.#.#.###.#######.###.#.##### +#.#.....#...#.#.....#.......#.....#.....# +#.#########.###############.###########.# +#.#x......#.#...#...........#...........# +#.#.#####.#I#.#.#.#########.#####.#####.# +#.#...#...#...#...#.....#.#.#...#...#...# +#.###.#.#############.#.#.#.#.#.###.#.### +#...#.#.....#...#.....#...#.#.#.....#.#.# +#.###.###.#.#.#.###.#######.#.#######.#.# +#.#...#.#.#...#.....#.....#...#.....#...# +#.#M###.#.###########.###.#####.###.###.# +#.#...#.#...#..a..#...#.#.....#.#.....#.# +#.###.#.###.#.###.#.###.###.###.#.#####.# +#.........#...B.#.........#.....#.......# +######################################### \ No newline at end of file diff --git a/input/18b4.txt b/input/18b4.txt new file mode 100644 index 0000000..cc9b1f8 --- /dev/null +++ b/input/18b4.txt @@ -0,0 +1,41 @@ +######################################### +#@..#.........#...#.......#.....#.#.....# +#.###.#######.#.#.#.###.###.#.#.#.#.#.### +#.#...#.....#...#.#...#.....#y#...#.#...# +#.#.###.###.#####.###.#######.###.#.###Q# +#...#...#...#...#...#...#...#...#.#.#.#.# +#.###.#####.#.#.###.###.#.#.###.#.#.#.#.# +#.#...#...#...#.#.#.....#.#.#...#.#...#.# +#.###.#.#.#####.#.#######.#.#.#######.#.# +#...#...#.#...#.........#.#.#.#.......#.# +###.#.###.#.#.#########.#.#.#.#.#######.# +#..j#.#...#.#.....#.....#.#.#.....#.....# +#.###.#.###.#####.###.###.#.#######.###.# +#.#.#.#.....#...#...#...#.#.......#...#.# +#.#.#.#########.###.###.#.#######.###S#.# +#.#.............#.#.#.#.#.#.....#...#.#.# +#.#.###########.#.#.#.#.#.###.#.###.#.#.# +#.#.#.......#.#.#.#.#...#...#.#...#.#.#.# +#.#.#.#####.#.#.#.#.#######.###.#.#.#.#.# +#.#.#.#...#.#.....#.#.....#...#.#.#...#.# +#.###.#.#.#.#.#####.#.###.###.#.#######.# +#.....#.#.#.#...#...#.#...#...#...#...#.# +#######.#.#.###.#.#.###.###.###.#.#.#.#.# +#.......#.#.#...#.#...#.#...#...#...#...# +#.#####.#.#.#####.#.###.#.#######.####### +#.....#.#.#.....#.#.#...#.......#...#...# +#######.#######.#.#.#.###.#####.#####.#.# +#.....#.......#.#.#.#...#.#...#...#...#.# +#.###.#####.###.#.#####.#.#.#####.#.###.# +#.#.........#...#.#.....#...#.....#.#...# +#.###########.###.#.#######.#.#####.#.### +#.............#...#.....#.#.#.......#.O.# +#.###############.#####.#.#.#######.###.# +#.#...#...#.....#..m#...#...#.....#.#...# +#.#.#.#.#.#.###.###.#.#######.###.#.#.### +#.#.#...#.#...#.#...#.....#...#...#.#...# +#.#.#####.###.#.#.#######.#.###.#######.# +#p..#...#...#.#.#.#.......#.#...#.....#.# +#####.#####.#.#.#.#.#######.#.###.###.#.# +#.............#...#.......E.#.....#....c# +######################################### \ No newline at end of file diff --git a/src/18-helper.rkt b/src/18-helper.rkt new file mode 100644 index 0000000..ed4f1e8 --- /dev/null +++ b/src/18-helper.rkt @@ -0,0 +1,201 @@ +(require racket/set + data/heap + graph + (only-in 2htdp/batch-io read-lines) + (except-in "../lib.rkt" transpose)) + +(define input + (read-lines filename)) + +(define list-grid + (map string->list input)) + +(define vector-grid + (lists->vectors list-grid)) + +(define width + (length (car list-grid))) + +(define height + (length list-grid)) + +(define (show-grid) + (for-each displayln + (map (λ (s) + (string-replace (string-replace s "#" "█") "." " ")) + input))) + +;; keys : (setof char) +(define keys + (list->set + (cons #\@ + (map integer->char + (range (char->integer #\a) + (add1 (char->integer #\z))))))) + +;; coord : (list number number) + +;; get-char : coord -> char +(define (get-char coord) + (match-let ([(list x y) coord]) + (if (or (< x 0) (< y 0) + (>= x width) + (>= y height)) + #\# + (vector-ref (vector-ref vector-grid y) x)))) + +;; neighbours : coord -> (listof coord) +;; If the coordinate is occupiable (by a key, door, or bot), +;; return the neighbouring coordinates that are also occupiable +(define (neighbours coord) + (if (char=? #\# (get-char coord)) '() + (match-let ([(list x y) coord]) + (let ([U (list x (sub1 y))] + [D (list x (add1 y))] + [L (list (sub1 x) y)] + [R (list (add1 x) y)]) + (filter-not + (∘ (∂ char=? #\#) (∂ get-char)) + (list U D L R)))))) + +;; graph-grid : unweighted, undirected graph +;; Vertices are (char . coord) +;; Edges between traversable coordinates +(define graph-grid + (let* ([coords (cartesian-product (range 1 (sub1 width)) + (range 1 (sub1 height)))] + [graph (unweighted-graph/undirected '())]) + (for ([coord coords]) + (let ([ncoords (neighbours coord)]) + (for ([ncoord ncoords]) + (add-edge! graph + (cons (get-char coord) coord) + (cons (get-char ncoord) ncoord))))) + graph)) + +;; keys-hash : (hashof (char => coord)) +;; A hashmap from keys to their coordinates, including #\@ +(define keys-hash + (let ([hash (make-immutable-hash)] + [coords (cartesian-product (range 1 (sub1 width)) + (range 1 (sub1 height)))]) + (foldl (λ (coord hash) + (let ([char (get-char coord)]) + (if (or (char=? #\@ char) + (char-lower-case? char)) + (hash-set hash char coord) + hash))) + hash coords))) + +;; start : (char . coord) +;; Char-coord pair of #\@, for convenience +(define start + (cons #\@ (hash-ref keys-hash #\@))) + +;; visited : (setof char) +;; Assume that we already have any key that does not +;; show up in the hash, as well as the starting point #\@ +(define visited + (set-add (set-subtract keys (list->set (hash-keys keys-hash))) #\@)) + +;; inter-keys-hash : (hashof (char => char)) +;; A hashmap from keys to the keys that must be collected +;; when taking the shortest path from #\@ to that key +(define inter-keys-hash + (let ([hash (make-immutable-hash)]) + (foldl (λ (keycoord hash) + (match-let* ([(cons key _) keycoord] + [path (map car (fewest-vertices-path graph-grid start keycoord))] + [inter-keys (remove* (list #\@ key) (filter char-lower-case? path))]) + (hash-set hash key inter-keys))) + hash (hash->list keys-hash)))) + +;; doors : (hashof (char => (listof char))) +;; A hashmap from keys to the list of keys for the doors +;; that stand between the starting point #\@ and that key +(define doors-hash + (let ([hash (make-immutable-hash)]) + (foldl (λ (keycoord hash) + (match-let* ([(cons key _) keycoord] + [path (map car (fewest-vertices-path graph-grid start keycoord))] + [doors (map char-downcase (filter char-upper-case? path))]) + (hash-set hash key doors))) + hash (hash->list keys-hash)))) + +;; key-graph : weighted, undirected graph +;; Vertices are char (keys) +;; Edges between neighbouring keys +;; Weights are distances between keys +(define key-graph + (let ([graph (weighted-graph/undirected '())] + [key-pairs (combinations (hash-keys keys-hash) 2)]) + (for ([pair key-pairs]) + (match-let* ([(list key1 key2) pair] + [keycoord1 (cons key1 (hash-ref keys-hash key1))] + [keycoord2 (cons key2 (hash-ref keys-hash key2))] + [path (fewest-vertices-path graph-grid keycoord1 keycoord2)] + [distance (sub1 (length path))]) + (add-edge! graph key1 key2 distance))) + graph)) + +;; visitable? : (setof char) -> char -> boolean +;; Given a set of visited keys and a prospective key, +;; return whether we visit that key based on three conditions: +;; - There isn't a closer key we could visit; +;; - The key has not yet been visited; and +;; - We have the keys needed to open all doors leading to that key. +(define (visitable? visited key) + (let* ([visitable-inter-keys (filter-not (∂ set-member? visited) (hash-ref inter-keys-hash key))]) + (and (empty? visitable-inter-keys) + (not (set-member? visited key)) + (andmap (∂ set-member? visited) (hash-ref doors-hash key))))) + +;; We do a breadth-first search over an implicit graph. +;; The nodes of this graph are visit states, which consist of +;; the current key we are at, as well as the keys we have visited. +;; This is why state=? only compares key and visited when removing +;; states from the heap. +;; States are ordered by the total number of steps taken, +;; which is also saved in the state struct. +;; This is why statelist input)) - -(define vector-grid - (lists->vectors list-grid)) - -(define width - (length (car list-grid))) - -(define height - (length list-grid)) - -(define (show-grid) - (for-each displayln - (map (λ (s) - (string-replace (string-replace s "#" "█") "." " ")) - input))) - -;; keys : (setof char) -(define keys - (list->set - (cons #\@ - (map integer->char - (range (char->integer #\a) - (add1 (char->integer #\z))))))) - -;; coord : (list number number) - -;; get-char : coord -> char -(define (get-char coord) - (match-let ([(list x y) coord]) - (if (or (< x 0) (< y 0) - (>= x width) - (>= y height)) - #\# - (vector-ref (vector-ref vector-grid y) x)))) - -;; neighbours : coord -> (listof coord) -;; If the coordinate is occupiable (by a key, door, or bot), -;; return the neighbouring coordinates that are also occupiable -(define (neighbours coord) - (if (char=? #\# (get-char coord)) '() - (match-let ([(list x y) coord]) - (let ([U (list x (sub1 y))] - [D (list x (add1 y))] - [L (list (sub1 x) y)] - [R (list (add1 x) y)]) - (filter-not - (∘ (∂ char=? #\#) (∂ get-char)) - (list U D L R)))))) - -;; graph-grid : unweighted, undirected graph -;; Vertices are (char . coord) -;; Edges between traversable coordinates -(define graph-grid - (let* ([coords (cartesian-product (range 1 (sub1 width)) - (range 1 (sub1 height)))] - [graph (unweighted-graph/undirected '())]) - (for ([coord coords]) - (let ([ncoords (neighbours coord)]) - (for ([ncoord ncoords]) - (add-edge! graph - (cons (get-char coord) coord) - (cons (get-char ncoord) ncoord))))) - graph)) - -;; keys-hash : (hashof (char => coord)) -;; A hashmap from keys to their coordinates, including #\@ -(define keys-hash - (let ([hash (make-immutable-hash)] - [coords (cartesian-product (range 1 (sub1 width)) - (range 1 (sub1 height)))]) - (foldl (λ (coord hash) - (let ([char (get-char coord)]) - (if (or (char=? #\@ char) - (char-lower-case? char)) - (hash-set hash char coord) - hash))) - hash coords))) - -;; start : (char . coord) -;; Char-coord pair of #\@, for convenience -(define start - (cons #\@ (hash-ref keys-hash #\@))) - -;; visited : (setof char) -;; Assume that we already have any key that does not -;; show up in the hash, as well as the starting point #\@ -(define visited - (set-add (set-subtract keys (list->set (hash-keys keys-hash))) #\@)) - -;; inter-keys-hash : (hashof (char => char)) -;; A hashmap from keys to the keys that must be collected -;; when taking the shortest path from #\@ to that key -(define inter-keys-hash - (let ([hash (make-immutable-hash)]) - (foldl (λ (keycoord hash) - (match-let* ([(cons key _) keycoord] - [path (map car (fewest-vertices-path graph-grid start keycoord))] - [inter-keys (remove* (list #\@ key) (filter char-lower-case? path))]) - (hash-set hash key inter-keys))) - hash (hash->list keys-hash)))) - -;; doors : (hashof (char => (listof char))) -;; A hashmap from keys to the list of keys for the doors -;; that stand between the starting point #\@ and that key -(define doors-hash - (let ([hash (make-immutable-hash)]) - (foldl (λ (keycoord hash) - (match-let* ([(cons key _) keycoord] - [path (map car (fewest-vertices-path graph-grid start keycoord))] - [doors (map char-downcase (filter char-upper-case? path))]) - (hash-set hash key doors))) - hash (hash->list keys-hash)))) - -;; key-graph : weighted, undirected graph -;; Vertices are char (keys) -;; Edges between neighbouring keys -;; Weights are distances between keys -(define key-graph - (let ([graph (weighted-graph/undirected '())] - [key-pairs (combinations (hash-keys keys-hash) 2)]) - (for ([pair key-pairs]) - (match-let* ([(list key1 key2) pair] - [keycoord1 (cons key1 (hash-ref keys-hash key1))] - [keycoord2 (cons key2 (hash-ref keys-hash key2))] - [path (fewest-vertices-path graph-grid keycoord1 keycoord2)] - [distance (sub1 (length path))]) - (add-edge! graph key1 key2 distance))) - graph)) - -;; visitable? : (setof char) -> char -> boolean -;; Given a set of visited keys and a prospective key, -;; return whether we visit that key based on three conditions: -;; - There isn't a closer key we could visit; -;; - The key has not yet been visited; and -;; - We have the keys needed to open all doors leading to that key. -(define (visitable? visited key) - (let* ([visitable-inter-keys (filter-not (∂ set-member? visited) (hash-ref inter-keys-hash key))]) - (and (empty? visitable-inter-keys) - (not (set-member? visited key)) - (andmap (∂ set-member? visited) (hash-ref doors-hash key))))) - -;; We do a breadth-first search over an implicit graph. -;; The nodes of this graph are visit states, which consist of -;; the current key we are at, as well as the keys we have visited. -;; This is why state=? only compares key and visited when removing -;; states from the heap. -;; States are ordered by the total number of steps taken, -;; which is also saved in the state struct. -;; This is why state