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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
(define-module (day15)
#:use-module (ice-9 rdelim)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-26))
(define (rectangle-fold proc init i0 ipast j0 jpast)
(let iloop ((i i0) (result init))
(if (>= i ipast)
result
(let jloop ((j j0) (result result))
(if (>= j jpast)
(iloop (+ i 1) result)
(jloop (+ j 1) (proc i j result)))))))
(define-record-type
<problem> (%make-problem warehouse robot directions) problem?
#| A representation of a particular puzzle input. |#
(warehouse problem-warehouse #|
The 2-D array representing the warehouse. Characters must be ; ;
'#' for a wall, 'O' for an object, '.' for a point, '[' for the ; ;
first part of a big box and ']' for the second part. The ; ;
boundary must be made of wall characters, and characters '[' and ; ;
']' must always appear in consecutive pairs. |#)
(robot problem-robot #|
The position of the robot as a pair of integers in the bounds of ; ;
the warehouse. |#)
(directions problem-directions #|
The directions the robot follows, a string of characters like ; ;
'<', 'v', '>', or '^' for left, down, right, and up, ; ;
respectively. |#))
(define (ensure-warehouse-tile arr i j)
"Ensure the warehouse tile at the given position in the array is valid,
assuming that there is no box in the array boundaries."
(case (array-ref arr i j)
((#\[) (unless (char=? #\] (array-ref arr i (+ j 1)))
(error "Mismatched [ character in warehouse.")))
((#\]) (unless (char=? #\[ (array-ref arr i (- j 1)))
(error "Mismatched ] character in warehouse.")))
((#\# #\. #\O) #f)
(else "Expected warehouse tiles to be one of #, O, ., [, ]."
(array-ref arr i j))))
(define (ensure-warehouse obj)
"Ensure that the given object meets the conditions for being the
problem-warehouse of a problem."
(define (ensure-wall i j acc)
(unless (char=? #\# (array-ref obj i j))
(error "Expected the warehouse to have solid walls.")))
(unless (array? obj)
(error "Expected warehouse to be an array." obj))
(let ((dims (array-dimensions obj)))
(unless (= (length dims) 2)
(error "Expected 2-dimensional warehouse." obj))
(unless (every integer? dims)
(error "Expected warehouse to be 0-indexed." obj))
(let ((h (first dims)) (w (second dims)))
(unless (or (zero? h) (zero? w))
(rectangle-fold ensure-wall #f 0 1 0 w)
(rectangle-fold ensure-wall #f (- h 1) h 0 w)
(rectangle-fold ensure-wall #f 0 h 0 1)
(rectangle-fold ensure-wall #f 0 h (- w 1) w))
(rectangle-fold (lambda (i j acc) (ensure-warehouse-tile obj i j))
#f 0 h 0 w))))
(define (make-problem warehouse robot directions)
"Create a problem instance with the given data."
(ensure-warehouse warehouse)
(unless (and (pair? robot) (integer? (car robot)) (integer? (cdr robot)))
(error "Expected robot to be a pair of integers (i, j)."))
(unless (array-in-bounds? warehouse (car robot) (cdr robot))
(error "Expected the robot to be within the bounds of the warehouse."
robot warehouse))
(unless (string? directions)
(error "Expected the directions to be a string." directions))
(unless (string-every (lambda (c) (member c '(#\^ #\v #\< #\>))) directions)
(error "Expected the directions to be in [^v<>]." directions))
(%make-problem warehouse robot directions))
(define (read-problem port)
"Read the <problem> from the given input port."
(define robot #f)
(define (read-lines acc i)
(define line (read-line port))
(if (string=? line "")
(list->array 2 (reverse acc))
(begin
(and=> (and (not robot) (string-index line #\@))
(lambda (j)
(string-set! line j #\.)
(set! robot (cons i j))))
(read-lines (cons (string->list line) acc) (+ i 1)))))
(define warehouse (read-lines '() 0))
(define (read-directions acc)
(define line (read-line port))
(if (or (eof-object? line) (string=? line ""))
(string-concatenate (reverse acc))
(read-directions (cons line acc))))
(define directions (read-directions '()))
(make-problem warehouse robot directions))
(define (problem-dimensions problem)
"The dimensions of the warehouse in the problem as two values."
(apply values (array-dimensions (problem-warehouse problem))))
(define* (display-problem problem #:optional (port (current-output-port)))
"Display the problem object to the given output port."
(define-values (h w) (problem-dimensions problem))
(define (iter-warehouse i)
(unless (= i h)
(let ((line (list->string (array->list
(array-slice (problem-warehouse problem) i)))))
(when (= i (car (problem-robot problem)))
(string-set! line (cdr (problem-robot problem)) #\@))
(write-line line port))
(iter-warehouse (+ i 1))))
(iter-warehouse 0)
(newline port)
(write-line (problem-directions problem) port))
(define (char->direction c)
"Translate a (^,v,<,>) character into a (di,dj) pair of coordinates."
(case c
((#\^) '(-1 . 0))
((#\<) '(0 . -1))
((#\v) '(1 . 0))
((#\>) '(0 . 1))
(else (error "Invalid character."))))
(define (move-horizontal! problem dj)
"Move the robot left or right, for dj=-1,1, respectively."
(define mp (problem-warehouse problem))
(define i (car (problem-robot problem)))
(define j0 (cdr (problem-robot problem)))
(define (can-move? j)
(define nj (+ j dj))
(case (array-ref mp i nj)
((#\.) #t)
((#\#) #f)
(else (can-move? nj))))
(define (do-move! j prev)
(define nj (+ j dj))
(define nxt (array-ref mp i nj))
(array-set! mp prev i nj)
(unless (char=? nxt #\.) (do-move! nj nxt)))
(when (can-move? j0)
(do-move! j0 #\.)
(set-cdr! (problem-robot problem) (+ j0 dj))))
(define (move-vertical! problem di)
"Move the robot up or down, for di=-1,1, respectively."
(define mp (problem-warehouse problem))
(define i0 (car (problem-robot problem)))
(define j0 (cdr (problem-robot problem)))
(define to-move (make-hash-table))
(define gave-up? #f)
(define (scan-pos! i j)
(define c (array-ref mp i j))
(case c
((#\#) (set! gave-up? #t))
((#\O #\[ #\])
(unless (hash-ref to-move (cons i j))
(hash-set! to-move (cons i j) #t)
(when (char=? c #\[) (scan-pos! i (+ j 1))) ; I know
(when (char=? c #\]) (scan-pos! i (- j 1)))
(unless gave-up? (scan-pos! (+ i di) j))))))
(scan-pos! (+ i0 di) j0)
(unless gave-up?
(let ((lst (sort! (hash-map->list (lambda (k v) k) to-move)
(if (< di 0)
(lambda (a b) (< (car a) (car b)))
(lambda (a b) (> (car a) (car b)))))))
(for-each (lambda (pos)
(array-set! mp (array-ref mp (car pos) (cdr pos))
(+ (car pos) di) (cdr pos))
(array-set! mp #\. (car pos) (cdr pos)))
lst)
(set-car! (problem-robot problem) (+ i0 di)))))
(define (move! problem c)
"Move the robot in the direction given by the character."
(define diff (char->direction c))
(if (zero? (car diff))
(move-horizontal! problem (cdr diff))
(move-vertical! problem (car diff))))
(define (iterate! problem)
(string-for-each (cut move! problem <>) (problem-directions problem)))
(define (warehouse-value warehouse)
(define-values (h w) (apply values (array-dimensions warehouse)))
(rectangle-fold
(lambda (i j acc)
(+ acc (if (member (array-ref warehouse i j) '(#\O #\[))
(+ (* 100 i) j)
0)))
0 0 h 0 w))
(define* (part1 #:optional (port (current-input-port)))
(define problem (read-problem port))
(iterate! problem)
(display-problem problem)
(warehouse-value (problem-warehouse problem)))
(define (expand-problem problem)
"Expand the problem first problem to the second problem."
(define (expand-char c)
(case c
((#\#) '(#\# #\#))
((#\O) '(#\[ #\]))
((#\.) '(#\. #\.))
(else (error "Cannot expand character." c))))
(define (expand-line line)
(concatenate (map expand-char line)))
(define expanded-warehouse
(list->array 2 (map expand-line (array->list (problem-warehouse problem)))))
(define new-robot (cons (car (problem-robot problem))
(* 2 (cdr (problem-robot problem)))))
(make-problem expanded-warehouse new-robot (problem-directions problem)))
(define* (part2 #:optional (port (current-input-port)))
(define problem (expand-problem (read-problem port)))
(iterate! problem)
(display-problem problem)
(warehouse-value (problem-warehouse problem)))
|