#!/usr/local/bin/chicken-csi -ss (import (chicken io) (chicken sort) (chicken string) (chicken format) regex) (define-record monkey items inspect div true false counter) (set-record-printer! monkey (lambda (x o) (fprintf o "(monkey ~S ~S ~S ~S ~S)" (monkey-counter x) (monkey-div x) (monkey-true x) (monkey-false x) (monkey-items x)))) ;; to make parsing work line-by-line, we store the divisor, ;; true-monkey and false-monkey in separate slots. This helper ;; simulates the entire test sequence: given a value, spit out ;; which monkey to hand it to next (define (monkey-test m v) (if (= 0 (modulo v (monkey-div m))) (monkey-true m) (monkey-false m))) ;; helper constructor for parsing (define (new-monkey) (make-monkey #f #f #f #f #f 0)) ;; meat of the simulation (see main for notes on the 'reducer') (define (take-turn m flock reducer) (let ((items (monkey-items m))) (if (null? items) 'done (let* ((new-val (reducer ((monkey-inspect m) (car items)))) (next-monkey-idx (monkey-test m new-val)) (next-monkey (vector-ref flock next-monkey-idx))) (monkey-counter-set! m (add1 (monkey-counter m))) (monkey-items-set! m (cdr items)) (monkey-items-set! next-monkey (append (monkey-items next-monkey) (list new-val))) (take-turn m flock reducer))))) ; recurse for next item ;; simple countdown loop that passes args (define (simul flock rounds reducer) (if (= rounds 0) 'done (begin (for-each (lambda (m) (take-turn m flock reducer)) (vector->list flock)) (simul flock (sub1 rounds) reducer)))) ;; parse the input line-by-line into a vector of monkey records ;; TODO: would be nice to use regex instead of equal?... (this was a doozy) (define (parse-input lines) (let loop ((flock '()) (lines lines) (m (new-monkey))) (if (null? lines) (apply vector (append flock (list m))) (let ((parts (string-split (car lines) " ,"))) (cond ((null? parts) ;; start the next monkey (loop (append flock (list m)) (cdr lines) (new-monkey))) ((equal? (car parts) "Monkey") ; just skip (loop flock (cdr lines) m)) ((equal? (car parts) "Starting") (monkey-items-set! m (map string->number (cddr parts))) (loop flock (cdr lines) m)) ((equal? (car parts) "Operation:") (let* ((opstr (list-ref parts 4)) (valstr (list-ref parts 5)) (op (cond ((equal? opstr "*") *) ((equal? opstr "+") +) (else "Bad op: " (car lines))))) ;; special case "old" (if (equal? valstr "old") (monkey-inspect-set! m (lambda (o) (op o o))) (monkey-inspect-set! m (lambda (o) (op o (string->number valstr))))) (loop flock (cdr lines) m))) ((equal? (car parts) "Test:") (monkey-div-set! m (string->number (list-ref parts 3))) (loop flock (cdr lines) m)) ((equal? (car parts) "If") (cond ((equal? (cadr parts) "true:") (monkey-true-set! m (string->number (list-ref parts 5)))) ((equal? (cadr parts) "false:") (monkey-false-set! m (string->number (list-ref parts 5)))) (else (error "Bad line " (car lines)))) (loop flock (cdr lines) m)) (else (error "Bad line " (car lines)))))))) (define (reset-counters! flock) (for-each (lambda (m) (monkey-counter-set! m 0)) (vector->list flock))) (define (score flock) (let ((sorted (sort (map monkey-counter (vector->list flock)) >))) (* (car sorted) (cadr sorted)))) (define (main args) (let* ((lines (read-lines)) (flock (parse-input lines)) (flock-lcm (apply * (map monkey-div (vector->list flock))))) ;; For part 1, we reduce the fear by floor(v / 3) (simul flock 20 (lambda (v) (floor (/ v 3)))) (print (score flock)) (reset-counters! flock) ;; For part 2, we need to be smarter about how we manage ;; the explosive fear growth (into bignum territory). ;; What do monkeys REALLY care about? They care about the ;; divisibility by the fear. If monkeys 1, 2, and 3 ;; care about divisibility A B C, then (lcm A B C) is the ;; highest number we care about -> we can loop around at LCM ;; since the divisibility of (X % LCM) = X. (simul flock 10000 (lambda (v) (modulo v flock-lcm))) (print (score flock))))