blob: 5eac027a8b02d01ca32bb65a027616b2ead19532 (
plain)
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
|
(library (lib)
(export solve-part1 solve-part2)
(import (chezscheme))
; Global state... a sin, but very convenient in this case!
(define chars '()) ; Characters still to be parsed
(define part1-total 0) ; Total score of groups in input
(define part2-total 0) ; Total non-escaped garbage chars
; Is the next character in the stream equal to `c'?
(define (peek? c)
(char=? c (car chars)))
; Blindly accept character and pop it. We do all detection
; via `peek?', so we don't care what gets discarded here.
(define (next)
(set! chars (cdr chars)))
; Parse a "group". We can assume all input is well-formed,
; which really helps keep this code as simple as possible.
(define (parse-group depth)
(next) ; Accept initial #\{
(let loop ()
; Expect start of "group" or "garbage"
(cond
((peek? #\{)
(parse-group (+ depth 1)))
((peek? #\<)
(parse-garbage)))
; Expect comma or end of current "group"
(cond
((peek? #\,)
(next) ; Accept #\,
(loop)) ; Parse next "group"/"garbage"
((peek? #\})
; Add current group's nesting depth to total score
(set! part1-total (+ part1-total depth))
(next))))) ; Accept #\} and return
; Parse "garbage": everything until next non-escaped #\>.
(define (parse-garbage)
(let loop ()
(next) ; Accept char (#\< at first)
(cond
((peek? #\>)
(next)) ; Accept #\> and return
((peek? #\!)
(next) ; Accept #\!
(loop)) ; Continue to escaped char
(else
; Add this character to the total count
(set! part2-total (+ part2-total 1))
(loop)))))
; Reset global parser state and parse the input string
(define (solve-puzzle str)
(set! chars (string->list str))
(set! part1-total 0)
(set! part2-total 0)
(parse-group 1))
(define (solve-part1 str)
(solve-puzzle str)
part1-total)
(define (solve-part2 str)
(solve-puzzle str)
part2-total)
)
|