I wanted to learn scheme and perhaps work through SICP. Guille seemed like a reasonable scheme to choose because I've also been considering learning Guix. Guix is a package manager similar to Nix, and it uses Guille as its configuration language.
Scheme/Guile
I was stuck on part 3 for a while, for not taking account that a child scale that I'm evaluating may already be a parent scale in some group.
(import (rnrs io ports (6))
(srfi srfi-1))
#!curly-infix
(define (parse-file file-name)
(let* ((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline))
(split-lines (map (lambda (l) (string-split l #\:)) lines))
(parsed-lines (map (lambda (l) (cons (string->number (car l)) (string->list (cadr l)))) split-lines)))
parsed-lines))
(define (child-score child p1 p2 p1-sim p2-sim)
(if (and-map null? (list child p1 p2))
(* p1-sim p2-sim)
(let ((matches-p1 (eq? (car child) (car p1)))
(matches-p2 (eq? (car child) (car p2))))
(cond
((not (or matches-p1 matches-p2)) #f)
(else (child-score (cdr child) (cdr p1) (cdr p2) (+ p1-sim (if matches-p1 1 0)) (+ p2-sim (if matches-p2 1 0))))))))
(let ((dna-lines (parse-file "notes/everybody_codes_e2025_q09_p1.txt")))
(format #t "P1 Answer: ~a\n\n" (or
(child-score (cdar dna-lines) (cdadr dna-lines) (cdaddr dna-lines) 0 0)
(child-score (cdadr dna-lines) (cdar dna-lines) (cdaddr dna-lines) 0 0)
(child-score (cdaddr dna-lines) (cdadr dna-lines) (cdar dna-lines) 0 0))))
(let ((dna-lines (list->vector (parse-file "notes/everybody_codes_e2025_q09_p2.txt"))))
(let loop ((child 0) (total-sim 0))
(if {child < (vector-length dna-lines)}
(loop (1+ child) (+ total-sim (let loop ((i 0))
(cond
((eq? i child) (loop (1+ i)))
({i >= {(vector-length dna-lines) - 1}} 0)
(else
(or
(let loop ((j (1+ i)))
(cond
((eq? j child) (loop (1+ j)))
({j >= (vector-length dna-lines)} #f)
(else (let ((res (child-score
(cdr (vector-ref dna-lines child))
(cdr (vector-ref dna-lines i))
(cdr (vector-ref dna-lines j)) 0 0)))
(or res (loop (1+ j)))))))
(loop (1+ i))))))))
(format #t "P2 Answer: ~a\n\n" total-sim))))
(define (init-id-to-group dna-lines)
(let ((table (make-hash-table)))
(let loop ((i 0))
(if {i < (vector-length dna-lines)}
(let ((id (car (vector-ref dna-lines i))))
(hash-set! table id id)
(loop (1+ i)))
table))))
(define (init-group-to-ids dna-lines)
(let ((table (make-hash-table)))
(let loop ((i 0))
(if {i < (vector-length dna-lines)}
(let ((id (car (vector-ref dna-lines i))))
(hash-set! table id (list id))
(loop (1+ i)))
table))))
(let ((dna-lines (list->vector (parse-file "notes/everybody_codes_e2025_q09_p3.txt"))))
(let ((id-to-group (init-id-to-group dna-lines)) (group-to-ids (init-group-to-ids dna-lines)))
(let child-loop ((child 0))
(if {child < (vector-length dna-lines)}
(let i-loop ((i 0))
(cond
((eq? i child) (i-loop (1+ i)))
({i >= {(vector-length dna-lines) - 1}} (child-loop (1+ child)))
(else
(let j-loop ((j (1+ i)))
(cond
((eq? j child) (j-loop (1+ j)))
({j >= (vector-length dna-lines)} (i-loop (1+ i)))
(else (let* ((cl (vector-ref dna-lines child))
(pil (vector-ref dna-lines i))
(pjl (vector-ref dna-lines j))
(res (child-score (cdr cl) (cdr pil) (cdr pjl) 0 0)))
(if res
(let* ((i-group (hash-ref id-to-group (car pil)))
(j-group (hash-ref id-to-group (car pjl)))
(child-group (hash-ref id-to-group (car cl)))
(i-group-ids (hash-ref group-to-ids i-group))
(j-group-ids (hash-ref group-to-ids j-group))
(child-group-ids (hash-ref group-to-ids child-group))
(new-group-ids (delete-duplicates (append child-group-ids (or i-group-ids '()) (or j-group-ids '())))))
(map (lambda (id) (hash-set! id-to-group id child-group)) new-group-ids)
(hash-remove! group-to-ids i-group)
(hash-remove! group-to-ids j-group)
(hash-set! group-to-ids child-group new-group-ids)
(child-loop (1+ child)))
(j-loop (1+ j))))))))))
(format #t "P3 Answer: ~a\n\n" (cdr (hash-fold
(lambda (_ id-list prior)
(let ((group-size (length id-list))
(group-sum (apply + id-list)))
(if {group-size > (car prior)}
(cons group-size group-sum)
prior)))
(cons 0 0)
group-to-ids)))))))
Scheme/Guile
Takes about 5 seconds.
(import (rnrs io ports (6))
(srfi srfi-1))
#!curly-infix
(define (parse-file file-name)
(let ((sequence (map string->number (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\,))))
(zip sequence (cdr sequence))))
(let loop ((sequence (parse-file "notes/everybody_codes_e2025_q08_p1.txt")) (count 0))
(if (null? sequence)
(format #t "P1 Answer: ~a\n\n" count)
(loop (cdr sequence) (+ count (if (and last (eq? (modulo (- (cadar sequence) (caar sequence)) 32) 16)) 1 0)))))
(define (crosses-over? a b)
(let ((a1 (car a))
(a2 (cadr a))
(b1 (car b))
(b2 (cadr b)))
(let ((a2 (modulo {a2 - a1} 256))
(b1 (modulo {b1 - a1} 256))
(b2 (modulo {b2 - a1} 256)))
(and (not (eq? b1 0)) (not (eq? b2 0))
(or
(and {b1 < a2} {b2 > a2})
(and {b1 > a2} {b2 < a2}))))))
(define (count-cross-overs sequence a)
(let loop ((sequence sequence) (count 0))
(if (null? sequence)
count
(loop (cdr sequence) (+ count (if (crosses-over? (car sequence) a) 1 0))))))
(let loop ((sequence (parse-file "notes/everybody_codes_e2025_q08_p2.txt")) (passed '()) (count 0))
(if (null? sequence)
(format #t "P2 Answer: ~a\n\n" count)
(loop (cdr sequence) (cons (car sequence) passed) (+ count (count-cross-overs passed (car sequence))))))
(let ((sequence (parse-file "notes/everybody_codes_e2025_q08_p3.txt")))
(let loop ((i 1) (greatest 0))
(if {i > 256}
(format #t "P3 Answer: ~a\n\n" greatest)
(loop (1+ i) (max greatest (let loop ((j i) (greatest 0))
(if {j > 256}
greatest
(loop (1+ j) (max greatest (count-cross-overs sequence (list i j)))))))))))
Scheme/Guile
You could probably build a (letter, length) => combination-count mapping pretty quickly for part 3, but dealing with overlap in the input elements seems like a pain if handled this way.
(import (rnrs io ports (6)))
#!curly-infix
(define (parse-file file-name) (let*
((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline))
(names (string-split (car lines) #\,))
(rule-lines (cddr lines))
(rules (map (lambda (l) (let*
((sides (string-split l #\space))
(r-left (string-ref (car sides) 0))
(r-right (caddr sides)))
(cons r-left (map (lambda (x) (string-ref x 0)) (string-split r-right #\,))))) rule-lines)))
(cons names rules)))
(define (check-name rules name)
(if (eq? 1 (string-length name))
#t
(let* ((letter (string-ref name 0))
(right (assq-ref rules letter))
(next-letter (string-ref name 1)))
(if (memq next-letter right)
(check-name rules (substring/read-only name 1))
#f))))
(let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p1.txt"))
(names (car parsed))
(rules (cdr parsed)))
(let loop ((names names))
(let* ((name (car names)) (name-matches (check-name rules name)))
(if name-matches
(format #t "P1 Answer: ~a\n\n" name)
(loop (cdr names))))))
(let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p2.txt"))
(names (car parsed))
(rules (cdr parsed)))
(let loop ((i 1) (names names) (name-sum 0))
(if (null? names)
(format #t "P2 Answer: ~a\n\n" name-sum)
(let* ((name (car names)) (name-matches (check-name rules name)))
(loop (1+ i) (cdr names) (+ name-sum (if name-matches i 0)))))))
(define discovered-prefixes (make-hash-table))
(define (count-prefixes rules name)
(if (hash-ref discovered-prefixes name)
0
(begin
(hash-set! discovered-prefixes name #t)
(if {(string-length name) >= 11}
1
(+
(apply + (map
(lambda (c) (count-prefixes rules (string-append name (string c))))
(or (assq-ref rules (string-ref name (1- (string-length name)))) '())))
(if {(string-length name) >= 7} 1 0))))))
(let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p3.txt"))
(names (car parsed))
(rules (cdr parsed)))
(let ((name-count (apply + (map (lambda (name) (count-prefixes rules name)) (filter (lambda (name) (check-name rules name)) names)))))
(format #t "P3 Answer: ~a\n\n" name-count)))
Scheme/Guile
Part 3 was a fun little challenge.
(import (rnrs io ports (6)))
#!curly-infix
(define (parse-file file-name) (string-trim-both (call-with-input-file file-name get-string-all)))
(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p1.txt"))
(line-length (string-length line)))
(let loop ((i 0) (knight-count 0) (mentor-count 0))
(if {i < line-length}
(let ((letter (string-ref line i)))
(loop (1+ i) (+ knight-count (if (eq? letter #\A) 1 0)) (+ mentor-count (if (eq? letter #\a) knight-count 0))))
(format #t "P1 Answer: ~a\n\n" mentor-count))))
(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p2.txt"))
(line-length (string-length line)))
(let loop ((i 0) (knight-counts '()) (mentor-count 0))
(if {i < line-length}
(let ((letter (string-ref line i)))
(loop
(1+ i)
(if (char-upper-case? letter) (assq-set! knight-counts letter (1+ (or (assq-ref knight-counts letter) 0))) knight-counts)
(+ mentor-count (if (char-lower-case? letter) (or (assq-ref knight-counts (char-upcase letter)) 0) 0))))
(format #t "P2 Answer: ~a\n\n" mentor-count))))
(let* ((line (parse-file "notes/everybody_codes_e2025_q06_p3.txt"))
(line-length (string-length line)))
(let loop ((i 0) (mentor-count 0))
(if {i < line-length}
(let ((letter (string-ref line i)))
(loop
(1+ i)
(+ mentor-count
(if (char-lower-case? letter)
(let loop ((j (- i 1000)) (mentors-here 0))
(if {j <= (+ i 1000)}
(loop
(1+ j)
(+ mentors-here
(if {(string-ref line {j modulo line-length}) eq? (char-upcase letter)}
(if (and {0 <= j} {j < line-length}) 1000 999)
0)))
mentors-here))
0))))
(format #t "P3 Answer: ~a\n\n" mentor-count))))
What a fiddlybit. I needed records to save me from list-hell on this one.
Scheme/Guile
(import (rnrs io ports (6))
(rnrs records syntactic))
(define (parse-file file-name)
(let* ((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline)))
(map (lambda (line)
(let* ((colon-split (string-split line #\:))
(segments (map string->number (string-split (cadr colon-split) #\,)))
(label (string->number (car colon-split))))
(cons label segments)))
lines)))
(define-record-type fishbone-segment (fields middle left right))
(define (construct-fishbone fishbone segments)
(if (null? segments)
fishbone
(let ((fishbone (add-fishbone-segment fishbone (car segments))))
(construct-fishbone fishbone (cdr segments)))))
(define (add-fishbone-segment fishbone segment)
(if (null? fishbone)
(list (make-fishbone-segment segment #f #f))
(let* ((fish-head (car fishbone))
(fish-middle (fishbone-segment-middle fish-head))
(fish-left (fishbone-segment-left fish-head))
(fish-right (fishbone-segment-right fish-head)))
(cond
((and (< segment fish-middle) (not fish-left)) (cons (make-fishbone-segment fish-middle segment fish-right) (cdr fishbone)))
((and (> segment fish-middle) (not fish-right)) (cons (make-fishbone-segment fish-middle fish-left segment) (cdr fishbone)))
(else (cons fish-head (add-fishbone-segment (cdr fishbone) segment)))))))
(define (score-fishbone fishbone)
(string->number (string-join (map (compose number->string fishbone-segment-middle) fishbone) "")))
(define-record-type sword (fields id fishbone quality))
(define (parse-swords file-name)
(map (lambda (sword-line)
(let ((fishbone (construct-fishbone '() (cdr sword-line))))
(make-sword (car sword-line) fishbone (score-fishbone fishbone))))
(parse-file file-name)))
(format #t "P1 Answer: ~a\n\n" (sword-quality (car (parse-swords "notes/everybody_codes_e2025_q05_p1.txt"))))
(let* ((swords (parse-swords "notes/everybody_codes_e2025_q05_p2.txt"))
(sword-scores (map sword-quality swords)))
(format #t "P2 Answer: ~a\n\n" (- (apply max sword-scores) (apply min sword-scores))))
(define (segment-score segment)
(string->number
(string-join
(map (lambda (n) (if (eqv? #f n) "" (number->string n)))
(list (fishbone-segment-left segment) (fishbone-segment-middle segment) (fishbone-segment-right segment)))
"")))
(define (sort-fishbones a b)
(if (null? a) '()
(let ((line-score-a (segment-score (car a)))
(line-score-b (segment-score (car b))))
(cond
((> line-score-a line-score-b) #t)
((< line-score-a line-score-b) #f)
(else (sort-fishbones (cdr a) (cdr b)))))))
(define (sort-swords a b)
(cond
((> (sword-quality a) (sword-quality b)) #t)
((< (sword-quality a) (sword-quality b)) #f)
(else (let ((fb-sort (sort-fishbones (sword-fishbone a) (sword-fishbone b))))
(cond
((null? fb-sort) (> (sword-id a) (sword-id b)))
(else fb-sort))))))
(let* ((swords (parse-swords "notes/everybody_codes_e2025_q05_p3.txt"))
(sorted-swords (sort swords sort-swords))
(swords-length (length swords)))
(let loop ((i 1) (total 0) (sorted-swords sorted-swords))
(if (<= i swords-length)
(loop (1+ i) (+ total (* i (sword-id (car sorted-swords)))) (cdr sorted-swords))
(format #t "P2 Answer: ~a\n\n" total))))
maaath
Scheme/Guile
(import (rnrs io ports (6)))
(define (parse-file file-name)
(map string->number (string-split (call-with-input-file file-name get-string-all) #\newline)))
(let* ((gears (parse-file "notes/everybody_codes_e2025_q04_p1.txt")))
(format #t "P1 Answer: ~a\n\n" (* 2025 (/ (car gears) (car (last-pair gears))))))
(let* ((gears (parse-file "notes/everybody_codes_e2025_q04_p2.txt")))
(format #t "P2 Answer: ~a\n\n" (ceiling (* 10000000000000 (/ (car (last-pair gears)) (car gears))))))
(define (parse-file-p3 file-name)
(map
(lambda (line) (map string->number(string-split line #\|)))
(string-split (call-with-input-file file-name get-string-all) #\newline)))
(let* ((gears (parse-file-p3 "notes/everybody_codes_e2025_q04_p3.txt")))
(format #t "P2 Answer: ~a\n\n"
(floor (* 100
(apply * (map (lambda (gear) (if (= 1 (length gear)) 1 (/ (cadr gear) (car gear)))) gears))
(/ (caar gears) (caar (last-pair gears)))))))
Scheme/Guile
Guile doesn't seem to come with a bag implementation :(. Not a big deal, a linear scan works about as well.
(use-modules (ice-9 rdelim))
(use-modules (srfi srfi-1))
(define (parse-file file-name)
(let* ((p (open-input-file file-name))
(comma-split (string-split (read-line p) #\,))
(number-list (map string->number comma-split)))
number-list))
(let* ((crates (parse-file "notes/everybody_codes_e2025_q03_p1.txt"))
(dedup-crates (delete-duplicates crates)))
(format #t "P1 Answer: ~a\n\n" (apply + dedup-crates)))
(let* ((crates (parse-file "notes/everybody_codes_e2025_q03_p2.txt"))
(dedup-crates (delete-duplicates crates))
(sorted-crates (sort dedup-crates <)))
(format #t "P2 Answer: ~a\n\n" (apply + (take sorted-crates 20))))
(let* ((crates (parse-file "notes/everybody_codes_e2025_q03_p3.txt"))
(sorted-crates (sort crates <))
(largest-set-size (let loop ((count 0) (l sorted-crates) (c #f) (max-count 0))
(if (nil? l)
max-count
(let* ((new-c (car l))
(new-count (if (equal? new-c c) (+ count 1) 1)))
(loop new-count (cdr l) new-c (max new-count max-count)))))))
(format #t "P3 Answer: ~a\n\n" largest-set-size))
I was stuck for a while on part 2. I thought I was running into precision errors, so I re-implemented everything using exact integers, then I re-implemented it in python, but it turns out that I just had an off-by-1 error. It just so happened that my off-by-1 algorithm gave the correct solution to the sample input. Part 3 takes a while to compute.
Scheme/Guile
(use-modules (ice-9 rdelim))
(use-modules (ice-9 format))
(use-modules (srfi srfi-1))
(define (parse-file file-name)
(let* ((p (open-input-file file-name))
(equality-split (string-split (read-line p) #\=))
(complex-str (list-ref equality-split 1))
(complex-parts (map string->number (string-split
(substring/read-only complex-str 1 (- (string-length complex-str) 1))
#\,))))
(+ (car complex-parts) (* 0+1i (list-ref complex-parts 1)))))
(define (special-div numerator divisor)
(+
(truncate/ (real-part numerator) (real-part divisor))
(* 0+1i (truncate/ (imag-part numerator) (imag-part divisor)))))
(let* ((A (parse-file "notes/everybody_codes_e2025_q02_p1.txt")))
(format #t "Input is ~a\n" A)
(let loop ((R 0+0i) (i 3))
(if (> i 0)
(loop (+ (special-div (* R R) 10+10i) A) (- i 1))
(format #t "P1 Answer: [~d,~a]\n\n"
(inexact->exact (real-part R))
(inexact->exact (imag-part R))))))
(define (check-engrave-range value)
(and-map (lambda (x) (<= (abs x) 1000000)) (list (real-part value) (imag-part value))))
(define (check-engrave point)
(let loop ((result 0+0i) (i 100))
(if (and (> i 0) (check-engrave-range result))
(loop (+ point (special-div (* result result) 100000+100000i)) (- i 1))
(check-engrave-range result))))
(let* ((origin (parse-file "notes/everybody_codes_e2025_q02_p2.txt"))
(engrave-count 0))
(format #t "Input is ~a\n" origin)
(let loop ((i 0))
(if (<= i 1000) (begin
(let loop ((j 0))
(if (<= j 1000) (begin
(set! engrave-count (+ engrave-count
(if (check-engrave (+ origin (+ i (* j 0+1i)))) 1 0)))
(loop (+ j 10)))))
(loop (+ i 10)))))
(format #t "P2 answer: ~a\n\n" engrave-count))
(let* ((origin (parse-file "notes/everybody_codes_e2025_q02_p3.txt"))
(engrave-count 0))
(format #t "Input is ~a\n" origin)
(let loop ((i 0))
(if (<= i 1000) (begin
(let loop ((j 0))
(if (<= j 1000) (begin
(set! engrave-count
(+ engrave-count (if (check-engrave (+ origin (+ i (* j 0+1i)))) 1 0)))
(loop (+ j 1)))))
(loop (+ i 1)))))
(format #t "P3 answer: ~a\n\n" engrave-count))
I'm still learning Scheme, but I wrote a solution in Guile:
(use-modules (ice-9 rdelim ))
(use-modules (srfi srfi-1))
(define (parse-step s) (*
(if (eq? (string-ref s 0) #\L) -1 1)
(string->number (substring s 1))
))
(define (parse-file file-name) (begin
(define p (open-input-file file-name))
(define names-str (read-line p))
(read-line p)
(define steps-str (read-line p))
(cons
(string-split names-str #\,)
(map parse-step (string-split steps-str #\,))
)
))
(let* ((parsed-file (parse-file "notes/everybody_codes_e2025_q01_p1.txt"))
(names (car parsed-file))
(steps (cdr parsed-file)))
(format #t "names: ~a\nsteps: ~a\n" names steps)
(define name-count (length names))
(define chosen-name-idx (reduce
(lambda (pos change) (max 0 (min (- name-count 1) (+ pos change))))
0 steps))
(format #t "P1: Chosen name(~a): ~a\n\n" chosen-name-idx (list-ref names chosen-name-idx))
)
(let* ((parsed-file (parse-file "notes/everybody_codes_e2025_q01_p2.txt"))
(names (car parsed-file))
(steps (cdr parsed-file)))
(format #t "names: ~a\nsteps: ~a\n" names steps)
(define name-count (length names))
(define chosen-name-idx (modulo (reduce + 0 steps) name-count))
(format #t "P2: Chosen name(~a): ~a\n\n" chosen-name-idx (list-ref names chosen-name-idx))
)
(let* ((parsed-file (parse-file "notes/everybody_codes_e2025_q01_p3.txt"))
(names (list->vector (car parsed-file)))
(steps (cdr parsed-file)))
(format #t "names: ~a\nsteps: ~a\n" names steps)
(define name-count (vector-length names))
(for-each (lambda (s)
(define head (vector-ref names 0))
(define swap-pos (modulo s name-count))
(vector-set! names 0 (vector-ref names swap-pos))
(vector-set! names swap-pos head)
) steps)
(format #t "P3: Chosen name: ~a\n" (vector-ref names 0))
)
As if they needed to make Kyto even more evil, lol.
The show does a good job of making me to want to see the progression of Makio and Asa's relationship (also how Asa's mother changed after separating from Makio). I particularly liked the attorney in this episode. The Shingo-Makio pairing is ridiculously wholesome. Also, it definitely felt like they were planning to do a seamless ED transition.