14 Day 14
(require aoc-racket/day14) | package: aoc-racket |
The puzzle. Our input is a list of flying-reindeer descriptions — in particular, how fast they can fly, and how long they have to rest between flight sessions, e.g., Dancer can fly 27 km/s for 5 seconds, but then must rest for 132 seconds.
14.1 After 2503 seconds, what is the maximum distance any reindeer has flown?
For each reindeer, we have a description that specifies a) flight speed, b) flight time, and c) rest time. Thus, if we have the total flight time as input — and we do — we can use it to calculate flight distance. In other words, just as we made functions out of wire descriptions on Day 7, now we’ll make functions out of reindeer descriptions.
As in Day 7, we’ll use define-syntax to set up the reindeer functions. Each reindeer function will take a time in seconds and then use calc-distance to convert it into meters. After that, all we need to do is map the time over the reindeer and max to get the answer.
(require racket rackunit (for-syntax racket/file)) (provide (all-defined-out)) (define-syntax (convert-input-to-reindeer-functions stx) (syntax-case stx () [(_) (let* ([input-strings (file->lines "day14-input.txt")] [reindeer-strings (map (λ (str) (format "(reindeer ~a)" (string-downcase str))) input-strings)] [reindeer-datums (map (compose1 read open-input-string) reindeer-strings)]) (datum->syntax stx `(begin ,@reindeer-datums)))])) (define-syntax (reindeer stx) (syntax-case stx (can fly seconds but then must rest for) [(_ deer-name can fly speed km/s for fly-secs seconds,but then must rest for rest-secs seconds.) #'(define (deer-name total-secs) (calc-distance total-secs speed fly-secs rest-secs))] [else #'(void)])) (convert-input-to-reindeer-functions) (define (calc-distance total-secs speed fly-secs rest-secs) (let loop ([secs-remaining total-secs][distance 0]) (if (<= secs-remaining 0) distance (let ([secs-in-flight (min secs-remaining fly-secs)]) (loop (- secs-remaining fly-secs rest-secs) (+ (* secs-in-flight speed) distance))))))
(define (q1) (define seconds-to-travel 2503) (apply max (map (λ (deer-func) (deer-func seconds-to-travel)) (list dasher dancer prancer vixen comet cupid donner blitzen rudolph))))
14.2 Under the new rule, how many points does the winning reindeer have?
The new rule is that after each second of travel, the reindeer in the lead gets one point. Thus, the winner after 2503 seconds is not the reindeer that has traveled farthest, but that has gathered the most points — in other words, has been in the lead for the longest time.
This question is similar to the last. But instead of simulating one race, we have to simulate 2503 races, each one ending a second later than the last. After each second, we calculate the winning reindeer, and add it to our list of winners. After 2503 seconds, we find out how many times the winningest reindeer appears on the list. To do this, we’ll use the helper function frequency-hash from sugar/list. (You could also do it with for/fold, but managing nine reindeer in parallel is unwieldy. Just ask Santa.)
(require sugar/list) (define (q2) (define deer-funcs (list dasher dancer prancer vixen comet cupid donner blitzen rudolph)) (define winners (frequency-hash (flatten (for/list ([sec (in-range 1 (add1 2503))]) (define deer-results (map (λ (deer-func) (deer-func sec)) deer-funcs)) (define max-result (apply max deer-results)) (map (λ (deer-result deer-func) (if (= deer-result max-result) deer-func empty)) deer-results deer-funcs))))) (apply max (hash-values winners)))
14.3 Testing Day 14
(module+ test (define input-str (file->string "day14-input.txt")) (check-equal? (q1) 2640) (check-equal? (q2) 1102))