The driver was written by my profe. The solver was written by myself and my partner.
- Code: Select all
;; ---------------------------------------------
;; John Paxton
;; CS 436
;; October 11, 2005
;; ---------------------------------------------
;; driver.l
;; This file contains the driver for Program 3,
;; Sudoku.
;; ---------------------------------------------
;; ---------------------------------------------
;; solve
;; ---------------------------------------------
;; input-file: the name of the input file
;; that contains the Sudoku problem
;; ---------------------------------------------
;; This function is the main driver function
;; for Program 3.
;; ---------------------------------------------
(defun solve ( input-file )
(let*
(
;; problem to solve
(original (create-puzzle (make-array '(9 9)) input-file))
(solution (make-copy original)) ;; solution to problem
)
(print-puzzle original "The original problem")
(setf solution (time (solve-puzzle solution)))
(print-puzzle solution "The solved problem")
(format t "~2%Results:~%")
(format t "-------~%")
(if (initial-constraints-ok-p original solution)
(evaluate-all solution)
)
)
)
;; ---------------------------------------------
;; initial-constraints-ok-p
;; ---------------------------------------------
;; original: the original puzzle
;; solution: the proposed solution
;; ---------------------------------------------
;; Returns true if the "solution" is consistent
;; with the solved part of the "original" puzzle.
;; ---------------------------------------------
(defun initial-constraints-ok-p (original solution)
(let
(
(ok t) ;; are the constraints consistent?
(count 0) ;; the number of solved positions in the original problem
)
(dotimes (row (array-dimension original 0))
(dotimes (col (array-dimension original 1))
(if (numberp (aref original row col))
(if (equal (aref original row col) (aref solution row col))
(setf count (+ 1 count)) ;; consistent square
(setf ok nil) ;; inconsistent square
)
)
)
)
(if ok
(format t "All ~a initial constraints are ok.~%" count)
(format t "ERROR - some initial constraints are violated.~%")
)
ok
)
)
;; ---------------------------------------------
;; evaluate-all
;; ---------------------------------------------
;; solution: the proposed Sudoku solution
;; ---------------------------------------------
;; Evaluates each completely solved square in the
;; "solution" to decide if it violates any Sudoku
;; constraints. Prints the results.
;; ---------------------------------------------
(defun evaluate-all ( solution )
(let
(
(ok t) ;; are there any constraint violations?
(count 0) ;; number of solved squares
)
(dotimes (row (array-dimension solution 0))
(dotimes (col (array-dimension solution 1))
(cond
((numberp (aref solution row col))
(if (cell-ok-p row col solution)
(setf count (+ 1 count)) ;; this square is solved
(setf ok nil) ;; constraint violation
)
)
)
)
)
(if ok
(format t "The number of Sudoku cells solved is ~a.~%" count)
(format t "ERROR - there was at least one constraint violation.~%")
)
)
)
;; ---------------------------------------------
;; cell-ok-p
;; ---------------------------------------------
;; row: the row of the cell to examine
;; col: the column of the cell to examine
;; solution: the proposed Sudoku solution
;; ---------------------------------------------
;; Returns true if the cell being examined does
;; not violate any Sudoku constraints.
;; ---------------------------------------------
(defun cell-ok-p (row col solution)
(and
(line-ok-p solution (aref solution row col) row 0 0 1) ;; column
(line-ok-p solution (aref solution row col) 0 col 1 0) ;; row
(square-ok-p solution (aref solution row col) ;; 3 by 3 square
(* 3 (floor (/ row 3))) (* 3 (floor (/ col 3))))
)
)
;; ---------------------------------------------
;; square-ok-p
;; ---------------------------------------------
;; matrix: the matrix being examined
;; value: the value being sought
;; start-row: the starting row of the 3 by 3 square
;; start-col: the starting column of the 3 by 3 square
;; ---------------------------------------------
;; Returns true if the 3 by 3 square does not contain
;; more than 1 instance of "value".
;; ---------------------------------------------
(defun square-ok-p (matrix value start-row start-col)
(do
(
(count 0) ;; number of times "value" found
(row start-row (+ 1 row)) ;; current row
)
((>= row (+ start-row 3))
(if (> count 1) ;; print out violation information
(format t " ERROR - square violation in ~a ~a~%" start-row start-col)
)
(<= count 1) ;; loop exit value
)
(do
(
(col start-col (+ 1 col)) ;; current column
)
((>= col (+ start-col 3)))
(if (equal value (aref matrix row col))
(setf count (+ 1 count))
)
)
)
)
;; ---------------------------------------------
;; line-ok-p
;; ---------------------------------------------
;; matrix: the Sudoku puzzle
;; value: the value being sought
;; row: the starting row
;; col: the starting column
;; row-inc: how to increment the row
;; col-inc: how to increment the column
;; ---------------------------------------------
;; Go through the appropriate line (it might be
;; either a row or a column depending on the parameter
;; values) and find out whether "value" occurs
;; more than once. If it does, the Sudoku constraints
;; are violated.
;; ---------------------------------------------
(defun line-ok-p (matrix value row col row-inc col-inc)
(let
(
(count 0) ;; number of "value" occurrences
(original-row row) ;; the original row passed in
(original-col col) ;; the original column passed in
)
(dotimes (i (array-dimension matrix 0))
(if (equal value (aref matrix row col))
(setf count (+ 1 count))
)
(setf row (+ row row-inc))
(setf col (+ col col-inc))
)
(if (> count 1)
(format t " ERROR - line violation at ~a ~a ~%" original-row original-col)
)
(<= count 1) ;; return value
)
)
;; ---------------------------------------------
;; make-copy
;; ---------------------------------------------
;; original: the original Sudoku puzzle
;; ---------------------------------------------
;; Create and return a shallow copy of the "original".
;; The copy will be given to the Program 3 Sudoku
;; problem solvers.
;; ---------------------------------------------
(defun make-copy ( original )
(let
(
(copy (make-array `( ,(array-dimension original 0) ;; Sudoku copy
,(array-dimension original 1))))
)
(dotimes (row (array-dimension original 0) copy)
(dotimes (col (array-dimension original 1) copy)
(setf (aref copy row col) (aref original row col))
)
)
)
)
;; ---------------------------------------------
;; print-puzzle
;; ---------------------------------------------
;; matrix: representation of a Sudoku puzzle
;; message: an appropriate message to print
;; ---------------------------------------------
;; Prints out an appropriate representation of
;; a Sudoku puzzle. Any unsolved squares are
;; printed as question marks, ?.
;; ---------------------------------------------
(defun print-puzzle ( matrix message )
(format t "~%~a~2%" message)
(dotimes (row (array-dimension matrix 0))
(dotimes (col (array-dimension matrix 1))
(if (listp (aref matrix row col))
(format t "? ")
(format t "~a " (aref matrix row col))
)
)
(format t "~%")
)
(format t "-----------------~%")
)
;; ---------------------------------------------
;; create-puzzle
;; ---------------------------------------------
;; matrix: the initial representation of the Sudoku problem
;; file: the input file containing the problem
;; ---------------------------------------------
;; This function reads the appropropriate input
;; file in order to create an internal representation
;; of a Sudoku puzzle. Any question mark in the input
;; file is replaced with the list '(1 2 3 4 5 6 7 8 9)
;; representing the possible answers for this cell.
;; ---------------------------------------------
(defun create-puzzle ( matrix file )
(with-open-file (input-stream file :direction :input)
(dotimes (row (array-dimension matrix 0) matrix)
(dotimes (column (array-dimension matrix 1))
(setf (aref matrix row column) (read input-stream nil))
(if (equal '? (aref matrix row column))
(setf (aref matrix row column)
(generate-guesses (array-dimension matrix 0)))
)
)
)
)
)
;; ---------------------------------------------
;; generate-guesses
;; ---------------------------------------------
;; how-many
;; ---------------------------------------------
;; Make and return a *unique* list containing all of the
;; integers from 1 through "how-many".
;; ---------------------------------------------
(defun generate-guesses ( how-many )
(let
(
(result nil) ;; the resulting list
)
(dotimes (i how-many (reverse result))
(setf result (cons (+ 1 i) result))
)
)
)
;; ***** Here's solver.l
(defun solve-puzzle (board)
(let*
(
(size (array-dimension board 0))
(row-removal-array (make-array (list size)))
(col-removal-array (make-array (list size)))
(box-removal-array (make-array (list (/ size 3) (/ size 3))))
(initial-unknown) ;; the next unknown element list
(counter-timer 0) ;; the counter to time our program
(exit-on-count 150000) ;; when we should stop, if the program will run over 10 seconds
)
;; first we need to eliminate duplicates from rows, columns, and boxes
(dotimes (row size ) ;; go through each row
(dotimes (col size) ;; go through each col
(cond
( (atom (aref board row col))
(setf (aref row-removal-array row) (cons (aref board row col) (aref row-removal-array row))) ;; add element to list of elements to remove from row
(setf (aref col-removal-array col) (cons (aref board row col) (aref col-removal-array col))) ;; add element to list of elements to remove from column
(setf (aref box-removal-array (floor (/ row 3)) (floor (/ col 3))) (cons (aref board row col) (aref box-removal-array (floor (/ row 3)) (floor (/ col 3)))))
)
)
)
)
;; go through the rows again and columns and boxes and remove the elements from the removal arrays
(dotimes (row size ) ;; go through each row
(dotimes (col size) ;; go through each col
(cond
( (listp (aref board row col))
(dolist (number-to-remove (aref row-removal-array row))
(setf (aref board row col) (remove number-to-remove (aref board row col))) ;; remove from the board the list of elements to remove for this row
)
(dolist (number-to-remove (aref col-removal-array col))
(setf (aref board row col) (remove number-to-remove (aref board row col))) ;; remove from the board the list of elements to remove for this column
)
(dolist (number-to-remove (aref box-removal-array (floor (/ row 3)) (floor (/ col 3)))) ;; remove from the board the list of elements to remove for this box
(setf (aref board row col) (remove number-to-remove (aref board row col)))
)
)
)
)
)
;; we have now removed all obviously impossible placements
;;;;
;; implement a loop that checks for 1: singles in row/col 2: singles in box
;; 3: Pointing pairs? very effective, takes hardest.dat from 7sec to hardest2.dat 0.6 seconds.
;; scanraid.com/sudoku.htm
;; if done, we need to know how long until our program will reach 10 seconds
;; and need to quit
;;;;
(defun try (row col given-num-list)
(cond
( (> counter-timer exit-on-count) t )
(t
(do*
(
(num-list given-num-list)
(done-p nil) ;; whether a solution has been found
(num (first num-list) (first num-list)) ;; num is going through the list of elements possible for this location on the board
(next-row-col-list (find-next-unknown row col))
(next-row (first next-row-col-list))
(next-col (first (rest next-row-col-list)))
)
((or done-p (null num-list)) done-p) ;; do until done or num-list is null...
;; if num is in the row, col, or box, skip over calling (try... with this location)
(setf num-list (rest num-list)) ;; drop first item from num-list
(cond
( (can-exist-p row col num)
(setf (aref board row col) num) ;; set the place to the current num, cause it can exist here (thus far)
(cond
( (null next-row-col-list) ;; there board is full
;; we know the board is full and that each placement can exist, so we're done
(setf done-p t)
)
( t
(setf counter-timer (+ 1 counter-timer))
(setf done-p (try next-row next-col (aref board next-row next-col))) ;; recursive call on unknown
)
);; end inner cond
;; else, it can't exist in location given
)
)
(if (and (not done-p) (null num-list))
(setf (aref board row col) given-num-list) ;; reset the square we messed with, nothing worked here
)
;; let do* choose next number
)
)
)
)
;; checks if the given number can possibly exist in the row and col given
(defun can-exist-p (row col num)
;; check the row and see if the num is already in it
;; check the col, and box likewise
(let
(
(can-exist t) ;; assume it can exist until proven otherwise
)
(dotimes (current-row size) ;; check col
(if (eq (aref board current-row col) num)
(setf can-exist nil)
)
)
(if can-exist
(dotimes (current-col size) ;; check row
(if (eq (aref board row current-col) num)
(setf can-exist nil)
)
)
)
(if can-exist
(dotimes (current-row (/ size 3)) ;; check box
(dotimes (current-col (/ size 3))
(if (eq (aref board (+ (* 3 (floor (/ row 3))) current-row) (+ (* 3 (floor (/ col 3))) current-col)) num)
(setf can-exist nil)
)
)
)
)
can-exist
)
)
;; Finds the next list of elements in the board
(defun find-next-unknown (current-row current-col)
(do
(
(row current-row)
(col current-col)
(location)
)
( (or location (and (= row (- size 1)) (= col (- size 1)))) location) ;; do until the location is not an empty list, return location
(setf col (+ 1 col)) ;; add one to the column
(cond
(
(>= col size) ;; if the column is greater than the dimension of the board
(setf col 0) ;; wrap around, set column to 0
(setf row (+ 1 row)) ;; increment the row
)
)
;; check the row/col for a list of possible elements
(if (listp (aref board row col))
(setf location (list row col)) ;; error, out of bounds; check it *********************************************
)
)
)
;; time to try the search
;; find the first unknown element, call try on that element
(setf initial-unknown (find-next-unknown 0 -1))
(if initial-unknown
(try (first initial-unknown) (first (rest initial-unknown)) (aref board (first initial-unknown) (first (rest initial-unknown))))
)
)
board ;; return the solved board
)
Usage:
Fire up clisp.
(compile-file "sudoku.l")
(load "driver.l")
(load "sudoku")
(solve "somepuz.l")
Where "somepuz.l" is in the format:
? ? 4 ? ? 3 ? 2 ?
...
in a 9x9 square.