;; https://adventofcode.com/2021/day/4 ; good candidate for loop-acc (func get-all-boards (inp) (def ret (list)) (for-count (/ (length inp) 5) (redef ret (append ret (get-board (slice inp (+ (* 5 (- _idx_ 1)) 1) 5))))) ret) (func get-board (inp) (map get-row inp)) ; each row is a string of items ; separated by (potentially) several spaces ; return a list of just the strings (func get-row (row) (filter (lambda (x) (not (eq? "" x))) (split row " "))) (func process-boards (inp) (def raw-boards (filter (lambda (x) (not (eq? "" x))) inp)) (get-all-boards raw-boards)) (def lines (map strip (read-lines "input.txt"))) (def moves (split (first lines) ",")) ; moves is the first line (def boards (process-boards (rest lines))) ; this is a helper to work with zippers ; probably good to have in the stdlib (func unzip (lst idx) (list (reverse (slice lst 1 idx)) (slice lst (+ 1 idx)))) (func has-won-row (row) (reduce ;(apply and row)) (lambda (acc x) (and acc (int? x))) row #true)) (func has-won (board moves) (def checked (check-board board moves)) (def winner #false) ; check rows ; no break statement means we have to check everything, ; even after a winner is found ; can also use a while, but that would require more redefs (for-each checked (if (has-won-row _item_) (redef winner #true))) ; no break here means we check columns even if we won on rows ; check columns (for-count 5 (if (has-won-row (map (lambda (x) (first (slice x _idx_ 1))) checked)) (redef winner #true))) winner) ; this could (should?) be interior to check-board (func check-row (row moves) (map (lambda (x) (if (in? x moves) 0 ; use an integer x)) row)) (func check-board (board my-moves) (map (lambda (x) (check-row x my-moves)) board)) ; good candidate for stdlib (func extend (lst1 lst2) (reduce append lst2 lst1)) ; how do we better iterate through this list? ; called/next would be perfect for a zipper (def cur (list moves (list))) ; init zip (def winner #false) (while (not winner) (redef cur (unzip moves (- (length (first cur)) 1))) (def win-boards (drop-while boards (has-won _item_ (first cur)))) ; if win-boards is not empty, we found a winner! (if (not (nil? win-boards)) (block (redef winner #true) ; benefit of zipper -- easy to get recent elements! (def last-called (string->int (first (first (rest cur))))) ; result: a 25 element list of ints, where called numbers ; are a 0, and uncalled numbers are their integer equivalents (def winning-board (map (lambda (x) ; convert all strings to ints (if (string? x) (string->int x) x)) (reduce extend ; flatten the board from 5x5 to 1x25 (check-board ; check the board, called numbers become 0 (first win-boards) (append (first cur) (first (first (rest cur))))) (list)))) (print (->string winning-board)) (def sum-of-uncalled (apply + winning-board)) (print (concat "sum of uncalled: " (->string sum-of-uncalled))) (print (concat "last called: " (->string last-called))) (print (concat "answer: " (->string (* sum-of-uncalled last-called)))))))