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)))