Solver in Lisp

Programs which generate, solve, and analyze Sudoku puzzles

Solver in Lisp

Postby sintaks » Tue Jan 17, 2006 5:03 pm

Probably been one of these before... oh well. It was fun.

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.
sintaks
 
Posts: 1
Joined: 17 January 2006

Return to Software