Skip to content
sudoku-algorithm.lisp 14.5 KiB
Newer Older
Vuong's avatar
Vuong committed
(in-package :sudoku-algorithm)

(defparameter *board-width* 9)

;;; This class represents units that contain a list of cells.
Vuong's avatar
Vuong committed
;;; A unit can include cells in a row of Sudoku board,
;;; cells in a column of board, or cells in a square of board.
(defclass unit ()
  ((%cells :initarg :cells :accessor cells)))
Vuong's avatar
Vuong committed

(defun make-unit ()
  (make-instance 'unit :cells (make-list 0)))

Robert Strandh's avatar
Robert Strandh committed
;;; Given a unit, make sure this unit is the 
;;; parent of all its cells. 
(defun add-parent-to-cells-of-unit (unit)
  (loop for cell in (cells unit)
	do (push unit (parents cell))))
Vuong's avatar
Vuong committed

;;; This class represents cells that contain possible values.
;;; These cells also contain a list of units to which they belong.
Vuong's avatar
Vuong committed
(defclass cell ()
  ((%parents :initarg :parents :accessor parents)
   (%possibilities :initarg :possibilities :accessor possibilities)))
Vuong's avatar
Vuong committed

(defun make-cell (board-width)
Vuong's avatar
Vuong committed
  (make-instance 'cell
Robert Strandh's avatar
Robert Strandh committed
		 :parents '()
		 :possibilities (make-array board-width
                                            :element-type 'bit
                                            :initial-element 1)))
Vuong's avatar
Vuong committed

(defun erase-possibilities (cell)
  (loop for i from 0 below (length (possibilities cell))
	do (setf (aref (possibilities cell) i) 0)))
Vuong's avatar
Vuong committed

(defun set-possibilities (cell index value)
  (setf (aref (possibilities cell) index) value))

Robert Strandh's avatar
Robert Strandh committed
;;; return a true value if and only if there is more than one
;;; possible value for the cell. 
Vuong's avatar
Vuong committed
(defun check-hole (cell)
  (/= (symbolset-cardinality (possibilities cell)) 1))
Vuong's avatar
Vuong committed

(defun find-possibilities (cell)
  (let* ((leng (length (possibilities cell)))
	 (tmp (make-array leng :element-type 'bit :initial-element 1)))
Vuong's avatar
Vuong committed
    (loop for unit across (parents cell)
	  do (loop for cell1 across (cells unit)
		   do (unless (check-hole cell1)
			(let ((order-1 (position 1 (possibilities cell1))))
			  (setf (aref tmp order-1) 0)))))
    (setf (possibilities cell) tmp)))

;;; This class represents a Sudoku board.
;;; It includes an array of symbols that is used to display characters,
;;; an array of cells in the board,
;;; and a list of all units (row-units, column-units, square-units).
Vuong's avatar
Vuong committed
(defclass game ()
  ((%symbols :initarg :symbols :accessor symbols)
   (%cells :initarg :cells :accessor cells)
   (%units :initarg :units :accessor units)))
Vuong's avatar
Vuong committed

(defun make-game (board-width)
Vuong's avatar
Vuong committed
  (make-instance 'game
		 :symbols (make-array board-width)
		 :cells (make-array (list board-width board-width))
Robert Strandh's avatar
Robert Strandh committed
		 :units '()))
Vuong's avatar
Vuong committed

(defun create-mapping-vector (board-width)
  (let ((v (make-array board-width)))
Vuong's avatar
Vuong committed
    (loop for i from 0 below board-width
	  do (setf (aref v i) (1+ i)))
    ;;(shuffle-vector v) This statement is commented for easy checking
    (return-from create-mapping-vector v)))
Vuong's avatar
Vuong committed

(defun map-over-rectangle (fun game start-row height start-col width)
  (loop for row from start-row
	repeat height
	do (loop for col from start-col
		 repeat width
		 do (funcall fun (aref (cells game) row col)))))

(defun copy-rectangle-to-unit (game start-row height start-col width unit)
  (map-over-rectangle (lambda (cell) (push cell unit))
		      game start-row height start-col width))
Vuong's avatar
Vuong committed

(defun symbolset-cardinality (symbolset)
  (count 1 symbolset))

(defun symbolset-union (symbolset1 symbolset2)
  (bit-ior symbolset1 symbolset2))

(defun symbolset-difference (symbolset1 symbolset2)
  (bit-andc2 symbolset1 symbolset2))

(defun create-unit-for-testing (choice)
  (let ((board-width *board-width*)
	(unit (make-unit))
	(unit1 (make-unit))
Vuong's avatar
Vuong committed
	(unit2 (make-unit))
	(unit3 (make-unit)))
    ;; initialize cells for unit1
    ;; strategy1 can be applied for unit1
    (loop for i from 0 below board-width
	  do (let ((cell (make-cell board-width)))
Vuong's avatar
Vuong committed
	       (erase-possibilities cell)
	       (set-possibilities cell i 1)
	       (push cell (cells unit1))))
    (setf (possibilities (second (cells unit1))) #*000000110)
    ;; initialize cells for unit2
    ;; strategy2 can be applied for unit2
    (loop for i from 0 below board-width
	  do (let ((cell (make-cell board-width)))
Vuong's avatar
Vuong committed
	       (setf (possibilities cell) #*111111000)
	       (push cell (cells unit2))))
    (setf (possibilities (second (cells unit2))) #*000001100)
    (setf (possibilities (fourth (cells unit2))) #*000001010)
    ;; initialize cells for unit3
    ;; strategy3 can be applied for unit3
    (loop for i from 0 below board-width
	  do (let ((cell (make-cell board-width)))
Vuong's avatar
Vuong committed
	       (setf (possibilities cell) #*111111000)
	       (push cell (cells unit3))))
    (setf (possibilities (second (cells unit3))) #*001000000)
    (setf (possibilities (fourth (cells unit3))) #*000010000)
Vuong's avatar
Vuong committed
    (cond ((= choice 1) (setf (cells unit) (cells unit1)))
	  ((= choice 2) (setf (cells unit) (cells unit2)))
	  ((= choice 3) (setf (cells unit) (cells unit3))))
    (return-from create-unit-for-testing unit)))
Vuong's avatar
Vuong committed

(defun strategy1 (unit)
  (let ((ambiguous-cells (remove-if-not #'check-hole (cells unit))))
Vuong's avatar
Vuong committed
    (if (= (length ambiguous-cells) 1)
	;; then the strategy is applicable
	(let ((cell (first ambiguous-cells)))
	  (setf (possibilities cell)
		(symbolset-difference
		 (possibilities cell)
		 (reduce #'symbolset-union
			 (remove cell (cells unit))
			 :key #'possibilities)))
	  (return-from strategy1 (list cell)))
	;; else return an empty list
	(return-from strategy1 '()))))
Vuong's avatar
Vuong committed

(defun strategy2 (unit)
  (let ((ambiguous-cells (remove-if-not #'check-hole (cells unit))))
    (loop for cell in ambiguous-cells
	  do (let ((diff
		    (symbolset-difference
		     (possibilities cell)
		     (reduce #'symbolset-union
			     (remove cell (cells unit))
			     :key #'possibilities))))
Vuong's avatar
Vuong committed
	       (when (= (symbolset-cardinality diff) 1)
		 (setf (possibilities cell) diff)
		 (return-from strategy2 (list cell))))
	  finally (return-from strategy2 '()))))
Vuong's avatar
Vuong committed

(defun strategy3 (unit)
  (let ((ambiguous-cells (remove-if-not #'check-hole (cells unit)))
	(unchanged-cells (remove-if #'check-hole (cells unit))))
    (loop for cell in unchanged-cells
Vuong's avatar
Vuong committed
	  do (let ((order-1 (position 1 (possibilities cell))))
	       (loop for hole in ambiguous-cells
Vuong's avatar
Vuong committed
		     do (when (= (aref (possibilities hole) order-1) 1)
			  (set-possibilities hole order-1 0)
			  (return-from strategy3 (list hole)))))
	  finally (return-from strategy3 '()))))
Vuong's avatar
Vuong committed

(defun create-initial-board (board-width)
  (let* ((board (make-array (list board-width board-width)))
Vuong's avatar
Vuong committed
	 (sqrt-board-width (isqrt board-width)))
    (loop for offset1 from 0 below sqrt-board-width
	  for block-start from 0 below board-width by sqrt-board-width
	  do (loop for row from block-start
		   for offset2 from 0 by sqrt-board-width
		   repeat sqrt-board-width
		   do (loop for col from 0 below board-width
			    do (setf (aref board row col)
				     (mod (+ col offset1 offset2)
                                          board-width)))))
    (return-from create-initial-board board)))
Vuong's avatar
Vuong committed

;;; Exchange two non-overlapping rectangles having the same shape
(defun exchange-rectangles (board
                            start-row-1 start-col-1
                            height width
                            start-row-2 start-col-2)
Vuong's avatar
Vuong committed
  (unless (and (= start-row-1 start-row-2) (= start-col-1 start-col-2))
    (loop for row-1 from start-row-1
	  for row-2 from start-row-2
	  repeat height
	  do (loop for col-1 from start-col-1
		   for col-2 from start-col-2
		   repeat width
		   do (rotatef (aref board row-1 col-1)
			       (aref board row-2 col-2))))))

(defun exchange-rows (board row-1 row-2)
  (let ((board-width (array-dimension board 0)))
    (exchange-rectangles board row-1 0 1 board-width row-2 0)))

(defun exchange-columns (board col-1 col-2)
  (let ((board-height (array-dimension board 1)))
    (exchange-rectangles board 0 col-1 board-height 1 0 col-2)))

(defun exchange-blocks-of-rows (board row-1 row-2)
  (let* ((board-width (array-dimension board 0))
	 (sqrt-board-width (isqrt board-width)))
    (exchange-rectangles board row-1 0 sqrt-board-width board-width row-2 0)))

(defun exchange-blocks-of-columns (board col-1 col-2)
  (let* ((board-width (array-dimension board 0))
	 (sqrt-board-width (isqrt board-width)))
    (exchange-rectangles board 0 col-1 board-width sqrt-board-width 0 col-2)))

;;; This function computes a random permutation of n elements.  It
;;; works as follows: At each iteration, there is a prefix of i random
;;; elements (initially 0) , and another element of the prefix is
;;; computed by exchanging element i and a random element among the
;;; elements from i to n-1 inclusive.  There are n-i [(n-1) - i + 1]
;;; such elements, so the RANDOM function has to be called with n-i,
;;; which gives us a value between 0 and n-i-1 inclusive.  By adding i
;;; to that value, we get a value between i and n-1 inclusive.
;;; Instead of stipulating how the elements are stored, we take as an
;;; argument an exchange function that is called with i and the
;;; element number of the element to exchange with element i.  This function
;;; is responsible for accessing the exact physical representation of the n 
;;; elements. 
Vuong's avatar
Vuong committed
(defun compute-random-permutation (n exchange-fun)
  (loop for i from 0 to (- n 2)
	do (let ((e (+ i (random (- n i)))))
	     (unless (= i e)
	       (funcall exchange-fun i e)))))

;;; To shuffle a vector, compute a random permutation 
;;; of its elements. 
Vuong's avatar
Vuong committed
(defun shuffle-vector (vector)
  (compute-random-permutation
   (length vector)
   (lambda (i j)
     (rotatef (aref vector i) (aref vector j)))))
Vuong's avatar
Vuong committed

(defun shuffle-rows-within-block (board block-start)
  (let ((N (isqrt (array-dimension board 0))))
    (compute-random-permutation
     N (lambda (r1 r2)
         (exchange-rows board (+ block-start r1) (+ block-start r2))))))
Vuong's avatar
Vuong committed

(defun shuffle-columns-within-block (board block-start)
  (let ((N (isqrt (array-dimension board 0))))
    (compute-random-permutation
     N (lambda (c1 c2)
         (exchange-columns board (+ block-start c1) (+ block-start c2))))))
Vuong's avatar
Vuong committed

(defun shuffle-blocks-of-rows (board)
  (let ((N (isqrt (array-dimension board 0))))
    (compute-random-permutation
     N (lambda (r1 r2)
         (exchange-blocks-of-rows board (* r1 N) (* r2 N))))))
Vuong's avatar
Vuong committed

(defun shuffle-blocks-of-columns (board)
  (let ((N (isqrt (array-dimension board 0))))
    (compute-random-permutation
     N (lambda (c1 c2)
         (exchange-blocks-of-columns board (* c1 N) (* c2 N))))))
Vuong's avatar
Vuong committed

(defun shuffle-board (board)
  (let* ((board-width (array-dimension board 0))
	 (sqrt-board-width (isqrt board-width)))
    (loop for block-start from 0 below board-width by sqrt-board-width
       do (shuffle-rows-within-block board block-start))
    (loop for block-start from 0 below board-width by sqrt-board-width
       do (shuffle-columns-within-block board block-start))
    (shuffle-blocks-of-rows board)
    (shuffle-blocks-of-columns board)))

(defun set-sample-game (game)
  (let* ((board-width (array-dimension (cells game) 0))
	 (easy-board (make-array (list board-width board-width)
				 :initial-contents '((0 6 5 0 2 0 0 7 0)
						     (7 2 9 5 3 0 0 0 0)
						     (0 4 0 8 0 0 2 0 9)
						     (0 5 1 0 8 0 7 0 2)
						     (0 0 0 7 0 2 0 0 0)
						     (2 0 7 0 4 0 3 9 0)
						     (6 0 4 0 0 1 0 2 0)
						     (0 0 0 0 9 8 6 3 7)
						     (0 9 0 0 6 0 1 4 0))))
	 (hard-board (make-array (list board-width board-width)
				 :initial-contents '((0 0 0 3 0 8 0 5 4)
						     (8 5 0 6 9 0 0 0 0)
						     (2 0 0 0 4 0 0 8 0)
						     (6 0 0 0 0 0 7 0 1)
						     (7 2 0 0 0 0 0 4 5)
						     (3 0 5 0 0 0 0 0 2)
						     (0 7 0 0 5 0 0 0 3)
						     (0 0 0 0 8 6 0 2 9)
						     (4 6 0 9 0 3 0 0 0))))
	 (used-board (make-array (list board-width board-width))))
Vuong's avatar
Vuong committed
    (setf used-board easy-board)
    (loop for row from 0 below board-width
	  do (loop for col from 0 below board-width
Vuong's avatar
Vuong committed
		   for value = (aref used-board row col)
		   do (when (> value 0)
			(erase-possibilities (aref (cells game) row col))
			(set-possibilities (aref (cells game) row col)
                                           (- value 1) 1))))))
Vuong's avatar
Vuong committed

(defun create-game (board)
  (let* ((board-width (array-dimension board 0))
	 (sqrt-board-width (isqrt board-width))
	 (game (make-game board-width)))
    ;; Create mapping vector
    (setf (symbols game) (create-mapping-vector board-width))
Vuong's avatar
Vuong committed
    ;; Create an array of cell instances
    (loop for row from 0 below board-width
	  do (loop for col from 0 below board-width
		   do (setf (aref (cells game) row col)
                            (make-cell board-width))))
    ;; Initiate values for all cells of game
    ;; We will replace this call by the function which finds a game
Vuong's avatar
Vuong committed
    (set-sample-game game)
    ;; Create unit instances corresponding to rows
    (loop for row from 0 below board-width
	  do (let ((unit (make-unit)))
	       (copy-rectangle-to-unit game row 1 0 board-width unit)
	       (push unit (units game))
Robert Strandh's avatar
Robert Strandh committed
	       (add-parent-to-cells-of-unit unit)))
Vuong's avatar
Vuong committed
    ;; Create unit instances corresponding to columns
    (loop for col from 0 below board-width
	  do (let ((unit (make-unit)))
	       (copy-rectangle-to-unit game 0 board-width col 1 unit)
	       (push unit (units game))
Robert Strandh's avatar
Robert Strandh committed
	       (add-parent-to-cells-of-unit unit)))
Vuong's avatar
Vuong committed
    ;; Create unit instances corresponding to small squares
    (loop for row from 0 below board-width by sqrt-board-width
	  do (loop for col from 0 below board-width by sqrt-board-width
		   do (let ((unit (make-unit)))
			(copy-rectangle-to-unit
                         game row sqrt-board-width col sqrt-board-width unit)
Robert Strandh's avatar
Robert Strandh committed
			(add-parent-to-cells-of-unit unit))))
    (return-from create-game game)))
Vuong's avatar
Vuong committed

(defun copy-game (game)
  (let* ((board-width (array-dimension (cells game) 0))
	 (copied-game (make-game board-width)))
    (setf (symbols copied-game) (symbols game))
    (setf (cells copied-game) (cells game))
    (setf (units copied-game) (units game))
    (return-from copy-game copied-game)))
Vuong's avatar
Vuong committed

(defun print-board (game)
  (let ((board-width (array-dimension (cells game) 0)))
    (loop for row from 0 below board-width
	  do (format t "~%")
	     (loop for col from 0 below board-width
		   for cell = (aref (cells game) row col)
		   do (if (check-hole cell)
			  (format t "- ")
			  (let ((order-1 (position 1 (possibilities cell))))
			    (format t "~S " (aref (symbols game) order-1))))))))
Vuong's avatar
Vuong committed
  
(defun main ()
  (let* ((board (create-initial-board *board-width*))
	 (game (create-game board))
	 (copied-game (copy-game game)))
    (print-board game)
    (print-board copied-game)))

(defun test-strategy (choice)
  (let ((unit (create-unit-for-testing choice))
	(changed-cells nil))
    (loop for cell in (cells unit)
	  do (describe cell))
    (cond ((= choice 1) (setf changed-cells (strategy1 unit)))
	  ((= choice 2) (setf changed-cells (strategy2 unit)))
	  ((= choice 3) (setf changed-cells (strategy3 unit))))
    (format t "~%The changed cell is:")
    (describe (first changed-cells))))