Nov 14, 2009

Solving Sudoku using SQL Server 2005 - Step by Step - Part #5

Implementation of RunSolveAlgorithm3:

The last algorithm that we implemented was able to solve an easy puzzle. Now, lets take a hard one and see if the solution we have built till now in this series can solve it.

 EXEC SolveSudoku 
'030,001,000,,006,000,050,,500,000,983,,080,006,302,,000,050,000,,903,800,060,,714,000,009,,020,000,800,,000,400,030'

post-solve sudoku board - before implementing algorithm 3



post-solve solution board - before implementing algorithm 3



We see that the current solve methods cannot handle this puzzle. Lets build the next algorithm.

Implementation of RunSolveAlgorithm3:
The next algorithm is again an implementation from sudoku solver which they call the solve method B.
It is a little complex and I enjoyed implementing this one. You see that any row in the board will belong to three 3X3 block and the row will have 3 cells in each block. Now, check if there are any values in the row occur only in one of the 3 blocks. If it does, it means that for that particular block, the value exists in that row. So, that value can be removed from the other 6 cells in the block. We do the same for the columns.

We can do the check the other way round too, where any block will belong to 3 rows and will have 3 cells in each row. Now, check if there are any values in the block occur only in one of the 3 rows. If it does, it means that for that particular row, the value exists in that block. So, that value can be removed from the other 6 cells in the row. We can do it similarly for columns.

With the algorithm explained, here is the implementation:

ALTER PROC RunSolveAlgorithm3
AS
SET NOCOUNT ON
BEGIN
  DECLARE @RowCount SMALLINT,
          @Counter SMALLINT

  SET     @RowCount = 1
  SET     @Counter = 1

  WHILE(@RowCount > 0 AND dbo.VerifySolve() = 0)
  BEGIN
      SET @RowCount = 0;
      SET @Counter = 0;
      WHILE (@Counter <=9) 
      BEGIN  

        /*For each row, check if any number occur only in a specific block, then the number will be 
          in that row, remove it from all other rows in that block */
        WITH YSOL AS
        (
        SELECT YPOS,(XPOS-1)/3 + 1 AS XBLK, SUBSTRING(VAL,NUM,1) AS VAL FROM SOLUTION_BOARD A, NUMBERS B
                       WHERE B.NUM <=LEN(A.VAL)
                       AND LEN(VAL) > 1 
        GROUP BY YPOS,(XPOS-1)/3 + 1 ,SUBSTRING(VAL,NUM,1)  
        ),
        YSOL_DEL AS
        (
        SELECT D.NUM AS XPOS,C.NUM AS YPOS,A.VAL FROM YSOL A LEFT OUTER JOIN YSOL B ON A.YPOS = B.YPOS
        AND A.XBLK <> B.XBLK AND A.VAL= B.VAL 
        INNER JOIN NUMBERS C ON (A.YPOS -1)/3 =(C.NUM-1)/3
        INNER JOIN NUMBERS D ON A.XBLK =(D.NUM-1)/3+1
        WHERE B.YPOS IS NULL
        AND A.YPOS <> C.NUM
        AND A.VAL = @Counter
        )
        UPDATE SOL SET VAL = REPLACE(SOL.VAL,YDEL.VAL,'')
          FROM   SOLUTION_BOARD SOL, YSOL_DEL YDEL
          WHERE  SOL.XPOS = YDEL.XPOS
          AND SOL.YPOS = YDEL.YPOS    
          AND LEN(SOL.VAL) > 1;

        /*For each column, check if any number occur only in a specific block, then the number will be 
          in that column, remove it from all other columns in that block */
        WITH XSOL AS
        (
        SELECT XPOS,(YPOS-1)/3 + 1 AS YBLK, SUBSTRING(VAL,NUM,1) AS VAL FROM SOLUTION_BOARD A, NUMBERS B
                       WHERE B.NUM <=LEN(A.VAL)
                       AND LEN(VAL) > 1 
        GROUP BY XPOS,(YPOS-1)/3 + 1 ,SUBSTRING(VAL,NUM,1)  
        ),
        XSOL_DEL AS
        (
        SELECT D.NUM AS YPOS,C.NUM AS XPOS,A.VAL FROM XSOL A LEFT OUTER JOIN XSOL B ON A.XPOS = B.XPOS
        AND A.YBLK <> B.YBLK AND A.VAL= B.VAL 
        INNER JOIN NUMBERS C ON (A.XPOS -1)/3 =(C.NUM-1)/3
        INNER JOIN NUMBERS D ON A.YBLK =(D.NUM-1)/3+1
        WHERE B.XPOS IS NULL
        AND A.XPOS <> C.NUM
        AND A.VAL = @Counter
        )
        UPDATE SOL SET VAL = REPLACE(SOL.VAL,XDEL.VAL,'')
          FROM   SOLUTION_BOARD SOL, XSOL_DEL XDEL
          WHERE  SOL.YPOS = XDEL.YPOS
          AND SOL.XPOS = XDEL.XPOS    
          AND LEN(SOL.VAL) > 1;

        /*For each block, check if any number occur only in a specific row, then the number will be 
          in that block, remove it from all other blocks in that row*/
         WITH YBLK AS
         (
             SELECT ((YPOS-1)/3)*3 + (XPOS-1)/3 + 1 AS BPOS, YPOS, 
                    SUBSTRING(VAL,NUM,1) AS VAL 
             FROM   SOLUTION_BOARD A, NUMBERS B
             WHERE  B.NUM <=LEN(A.VAL)
                AND LEN(VAL) > 1 
             GROUP BY 
                   ((YPOS-1)/3)*3 + (XPOS-1)/3,YPOS,SUBSTRING(VAL,NUM,1)  
         ),
         YBLK_DEL AS 
         (
            SELECT  A.YPOS,C.NUM AS XPOS ,A.VAL 
            FROM YBLK A LEFT OUTER JOIN YBLK B 
            ON      A.BPOS = B.BPOS
                AND A.YPOS <> B.YPOS 
                AND A.VAL= B.VAL 
            INNER JOIN NUMBERS C ON 1 = 1
            WHERE B.YPOS IS NULL 
            AND A.BPOS = 8
            AND (C.NUM-1)/3 + 1 <> A.BPOS  - ((A.YPOS-1)/3)*3 
            AND A.VAL = @Counter
         )
         UPDATE SOL SET VAL = REPLACE(SOL.VAL,YDEL.VAL,'')
         FROM  SOLUTION_BOARD SOL, YBLK_DEL YDEL
         WHERE SOL.XPOS = YDEL.XPOS
           AND SOL.YPOS = YDEL.YPOS    
           AND LEN(SOL.VAL) > 1;
        
        /*For each block, check if any number occur only in a specific column, then the number will be 
          in that block, remove it from all other blocks in that column*/
         WITH XBLK AS
         (
            SELECT ((XPOS-1)/3)*3 + (YPOS-1)/3 + 1 AS BPOS, XPOS, 
                   SUBSTRING(VAL,NUM,1) AS VAL 
            FROM   SOLUTION_BOARD A, NUMBERS B
            WHERE  B.NUM <=LEN(A.VAL)
              AND  LEN(VAL) > 1 
             GROUP BY 
                  ((XPOS-1)/3)*3 + (YPOS-1)/3,XPOS,SUBSTRING(VAL,NUM,1)  
         ),
         XBLK_DEL AS 
         (
            SELECT A.XPOS, C.NUM AS YPOS ,A.VAL 
            FROM XBLK A LEFT OUTER JOIN XBLK B 
              ON     A.BPOS = B.BPOS
                 AND A.XPOS <> B.XPOS 
                 AND A.VAL= B.VAL 
            INNER JOIN NUMBERS C ON 1 = 1
            WHERE B.XPOS IS NULL AND 
            A.BPOS = 8
            AND (C.NUM-1)/3 + 1 <> A.BPOS  - ((A.XPOS-1)/3)*3 
            AND A.VAL = @Counter
         )
         UPDATE SOL SET VAL = REPLACE(SOL.VAL,XDEL.VAL,'')
         FROM  SOLUTION_BOARD SOL, XBLK_DEL XDEL
         WHERE SOL.YPOS = XDEL.YPOS
           AND SOL.XPOS = XDEL.XPOS    
           AND LEN(SOL.VAL) > 1;

        SET @Counter = @Counter + 1
     END

    /* If the above updates led to a determining the value of any cell in the solution 
           board (Only one digit exists in the cell), then we update the sudoku_board */
    UPDATE SUD
    SET VAL = SOL.VAL
    FROM SUDOKU_BOARD SUD, SOLUTION_BOARD SOL
    WHERE SUD.XPOS = SOL.XPOS
      AND SUD.YPOS = SOL.YPOS
      AND LEN(SOL.VAL) = 1
      AND SUD.VAL IS NULL

    SET @RowCount = @@ROWCOUNT    
    
    /* We rerun SolveAlgorithms 1 and 2 to see if there are any more solves possible */

    EXEC RunSolveAlgorithm1;
    EXEC RunSolveAlgorithm2;
  END
END
GO

post-solve sudoku board - after implementing Algorithm 3 (Solved)




With this implementation, we should be able to solve most medium and some hard puzzles. There are a lot more well known algorithms available to solve the puzzle logically, which should be equally interesting to implement. If you come up with an implementation other than the ones given here, please feel free to post the link or the actual implementation in the comments section. But, I am done with my implementations for now. The next post, our last algorithm, will be a brute force algorithm, which will be a fall back in case our first 3 algorithms are not able to solve the puzzle.

0 comments:

Post a Comment