|(require aoc-racket/day01)||package: aoc-racket|
The building has an indefinite number of floors in both directions. So the ultimate destination is just the number of up movements minus the number of down movements. In other words, a left parenthesis = 1 and a right parenthesis = -1, and we sum them.
regexp-match* will return a list of all occurrences of one string within another. The length of this list is the number of occurrences. Therefore, we can use it to count the ups and downs.
(require racket rackunit) (provide (all-defined-out)) (define up-char #\() (define down-char #\)) (define (make-matcher c) (λ (str) (length (regexp-match* (regexp (format "\\~a" c)) str)))) (define get-ups (make-matcher up-char)) (define get-downs (make-matcher down-char)) (define (get-destination str) (- (get-ups str) (get-downs str)))
(define (q1 str) (get-destination str))
Rather than counting matches with regexp-match*, we could also convert the string of parentheses directly into a list of numbers.
The elevator is in the basement whenever it’s at a negative-valued floor. So instead of looking at its ultimate destination, we need to follow the elevator along its travels, computing its intermediate destinations, and stop as soon as it reaches a negative floor.
We could characterize this as a problem of tracking cumulative values or state. Either way, for/fold is the weapon of choice. We’ll determine the relative movement at each step, and collect these in a list. (The get-destination function is used within the loop to convert each parenthesis into a relative movement, either 1 or -1.) On each loop, for/fold checks the cumulative value of these positions, and stops when they imply a basement value. The length of this list is our answer.
When you need to stop a loop the first time a condition occurs, you can also consider for/first or for/or. The difference is that for/first ends after the first evaluation of the body, but for/or evaluates the body every time, and ends the first time the body is not #f.
The two are similar. The choice comes down to readability and efficiency — meaning, if each iteration of the loop is expensive, you’ll probably want to cache intermediate values, which means you might as well use for/fold.
(define (q2-for/first str) (define basement-position (let ([ints (elevator-string->ints str)]) (for/first ([idx (in-range (length ints))] #:when (negative? (apply + (take ints idx)))) idx))) basement-position) (define (q2-for/or str) (define basement-position (let ([ints (elevator-string->ints str)]) (for/or ([idx (in-range (length ints))]) (and (negative? (apply + (take ints idx))) idx)))) basement-position)