(defgeneric push-char (accumulator value)
(:documentation "Process a character from the input."))
(defgeneric peek (accumulator)
(:documentation "Determine whether or not a \"digit\" has been found."))
(defclass digit-accumulator ()
((current-digit :initform nil)))
(defmethod push-char ((accumulator digit-accumulator) value)
(setf (slot-value accumulator 'current-digit) (digit-char-p value)))
(defmethod peek ((accumulator digit-accumulator))
(slot-value accumulator 'current-digit))
(defclass english-accumulator ()
((matching :initform nil)))
(defconstant english-numbers
(append (let ((ix 0))
(mapcar #'(lambda (name) (incf ix) (append (coerce name 'list) ix))
'("one" "two" "three" "four" "five"
"six" "seven" "eight" "nine" "ten")))
(loop
for n from 1 to 9
collect (append (coerce (format nil "~a" n) 'list) n))))
(defmethod push-char ((accumulator english-accumulator) value)
(setf (slot-value accumulator 'matching)
(mapcan
#'(lambda (entry)
(if (and (consp entry) (equal value (car entry)))
(list (cdr entry))))
(append (slot-value accumulator 'matching) english-numbers))))
(defmethod peek ((accumulator english-accumulator))
(dolist (entry (slot-value accumulator 'matching))
(if (numberp entry)
(return entry))))
(defun calibration (line)
(let ((accs
(mapcar
#'(lambda (which)
(list 'accumulator (make-instance which)'low () 'high ()))
'(digit-accumulator english-accumulator))))
(loop for c across line
do (dolist (ac accs)
(push-char (getf ac 'accumulator) c)
(let ((digit (peek (getf ac 'accumulator))))
(when digit
(setf (getf ac 'high) digit)
(unless (getf ac 'low) (setf (getf ac 'low) digit))))))
(mapcar
#'(lambda (acc)
(let ((low (getf acc 'low)) (high (getf acc 'high)))
(when (and low high) (+ (* 10 low) high))))
accs)))
(defun process (instream)
(let ((p1 0) (p2 0))
(loop for line = (read-line instream nil)
while line
do (destructuring-bind
(p1-1 p2-1)
(calibration line)
(setf p1 (+ p1 p1-1))
(setf p2 (+ p2 p2-1))))
(list p1 p2)))
(loop for arg in (cdr *posix-argv*)
do (with-open-file (s arg :direction :input)
(loop for result in (process s)
for part from 1
do (format T "Part ~a: ~a~%" part result))))