(ql:quickload 'split-sequence)
(defun read-data (path)
(with-open-file (stream path)
(loop for line = (read-line stream nil)
while line collect line)))
(defparameter *input-source*
(cadr *posix-argv*))
;; Useful in testing
;; (defparameter *input-source* "example_input")
;; (defparameter *input-source* "input")
(defparameter *data* (read-data *input-source*))
(defstruct board
board
x-marked-count
y-marked-count
value-index
unmarked-sum)
(defun build-value-index (arr)
(destructuring-bind (x y) (array-dimensions arr)
(loop for i from 0 below x
with table = (make-hash-table)
do (loop for j from 0 below y
do (setf (gethash (aref arr i j) table) (list i j)))
finally (return table))))
(defun sum-values (arr)
(destructuring-bind (x y) (array-dimensions arr)
(loop for i from 0 below x
sum (loop for j from 0 below y
sum (aref arr i j)))))
(defun new-board (numbers)
(let* ((dimension-x (length numbers))
(dimension-y (length (car numbers)))
(arr (make-array (list dimension-x dimension-y)
:initial-contents numbers)))
(make-board :board arr
:x-marked-count (make-array dimension-x)
:y-marked-count (make-array dimension-y)
:value-index (build-value-index arr)
:unmarked-sum (sum-values arr))))
(defun mark-drawn-number (number board)
(when (gethash number (board-value-index board))
(destructuring-bind (x y) (gethash number (board-value-index board))
(setf (board-unmarked-sum board) (- (board-unmarked-sum board) number))
(setf (aref (board-x-marked-count board) x) (1+ (aref (board-x-marked-count board) x)))
(setf (aref (board-y-marked-count board) y) (1+ (aref (board-y-marked-count board) y))))))
(defun board-win-p (board)
(destructuring-bind (x y) (array-dimensions (board-board board))
(or (some (lambda (v) (= v x)) (board-x-marked-count board))
(some (lambda (v) (= v y)) (board-y-marked-count board)))))
(defun parse-draws (draws)
(map 'list #'parse-integer
(split-sequence:split-sequence #\, draws)))
(defun parse-board-row (row)
(map 'list #'parse-integer
(split-sequence:split-sequence #\Space row :remove-empty-subseqs T)))
(defun parse-board (board)
(new-board (map 'list #'parse-board-row board)))
(defun parse-boards (boards)
(map 'list #'parse-board boards))
(defun parse-input (lines)
(let ((groups (split-sequence:split-sequence "" lines :test #'string=)))
(values (parse-draws (car (car groups)))
(parse-boards (cdr groups)))))
(defun play-bingo (draws boards)
(loop for number in draws
with winning-boards = '()
with playing-boards = boards
do (dolist (board playing-boards)
(mark-drawn-number number board))
do (setf winning-boards (remove-if-not #'board-win-p playing-boards))
do (setf playing-boards (remove-if #'board-win-p playing-boards))
when winning-boards
collect (list number winning-boards)
while playing-boards))
(defun part1 (scores)
(destructuring-bind (number boards) (car scores)
(* number
(board-unmarked-sum (car boards)))))
(defun part2 (scores)
(destructuring-bind (number boards) (car (last scores))
(* number
(board-unmarked-sum (car (last boards))))))
(time
(multiple-value-bind (draws boards) (parse-input *data*)
(let ((scores (play-bingo draws boards)))
(format t "Part 1: ~A~%" (part1 scores))
(format t "Part 2: ~A~%" (part2 scores)))))