Logical Genetics Forums

Profile Profile FAQ FAQ Calendar Calendar Log in Log in Register Register

Sudoku Solver

View previous topic :: View next topic
Post new topic     Reply to topic

Logical Genetics Forum Index » Artificial Intelligence » Sudoku Solver


Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Mon Aug 08, 2005 1:46 pm Post subject: Sudoku Solver Reply with quote
At lunch today people were chatting about Sudoku puzzles. I was struck by how simple they would be (at least from an application development point of view) to solve with an Evolutionary Algorithm - especially using my snazzy little framework...

Basically you'd need a population of individuals, each of which is made up of a 9x9 grid of digits. There must be nine each of the digits 0 to 9 (so nine ones, nine twos etc). Some of these digits are given to us by the puzzle designer and must be "fixed", the others must be arranged in such a way that no single digit is repeated in any column, row or in any of the nine 3x3 "sub-boxes".

Some fitness function must be used to see how good each solution is - for example by giving a count of the number of repeated digits in the solution. The EA kills off the worst solutions and breeds replacements from the remaining population.

Breeding new solutions is done using crossover of the two parents genetic material and mutation of the child. Crossover is dead easy; we simply take chunks of each parent's solution to make the child (so the child could be made of the top half of Mum and the bottom half of Dad). To do mutation we just randomly swap two digits in the child solution (so we might take the digit from square [2,4] and swap it with square [7,8]).

Not hard in the slightest - but how well would it do? Perhaps I shall code it and see

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
High Poster
Barbie

Joined: 01 Jan 2001
Posts: 713
Location: Reading
Post Posted: Mon Aug 08, 2005 2:07 pm Post subject: Reply with quote
Suduko is FAB. And if you can keep me in puzzles to solve then I'll be a very happy bunny!

Suduko rules!

L Angel
View user's profile Send private message Send e-mail Visit poster's website Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Mon Aug 08, 2005 3:30 pm Post subject: Reply with quote
I guess you could use the same system to create a load of puzzles - but there'd be no way of telling how hard they'd be!

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Mon Aug 08, 2005 11:05 pm Post subject: Reply with quote
Well, a very simple Sudoku Solver is now complete...

An image

In the picture above, the grey squares are the fixed digits of the original puzzle, while the white squares are the squares that can be changed - either by the user or the genetic algorithm. Red numbers are numbers which clash in some way.

I have to say that I've never come across an EA Application more prone to local minima than this one. It's easy to get the first few numbers in, but after that, the probability of swapping the correct numbers is so low that solution speed really drops off. This is made worse by over fitting to not-quite-perfect solutions which mean that, in order to get to the optimum solution, we have to go via a series of solutions which are far less good that the current one (In English: we have to shake it up and start again!).

So, though it's been very easy to get this far, I'm not so sure that an Evolutionary Algorithm is the best tool for solving a "pure logic" puzzle like this one.

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Medium Poster
ballista

Joined: 01 Jan 2001
Posts: 451
Location: London
Post Posted: Tue Aug 09, 2005 8:41 am Post subject: Reply with quote
The first of these stupid puzzles that I came across stated that it required 2 hours to solve, so I spent 2 hours implementing a depth-first search solver. I haven't bothered to put a graphical interface on it, but I can let you have the code if you are interested. It implements the optimisation that it guesses squares in the most complete constraint-group first (line, column or square). This reduces the branching factor of the search tree. I can't think of any particularly good heuristics, although there probably are some.

I find it hard to understand why people like doing these puzzles manually. Most people probably do an approximately depth-first search, so once they have figured out the algorithm, the only thing they are fighting against it their own capacity to make errors.

P.S. Jo and I had a nice holiday on the canal boat, thank you, and will be coming to your bar-be-que.

View user's profile Send private message Send e-mail Back to top
Medium Poster
ballista

Joined: 01 Jan 2001
Posts: 451
Location: London
Post Posted: Tue Aug 09, 2005 8:47 am Post subject: Reply with quote
On the subject of generating puzzles, I think you can do this with DFS too. You just start searching for solutions on an empty grid. Or you could seed the grid with a few starting numbers. At some level of the search tree you say, 'OK, those are my starting numbers' you then keep searching to verify that there is a unique solution (not sure if this is always feasible, as you have to search the complete space, although given the size of the puzzle and the constraints of the starting numbers this will not always be huge).

The difficulty of the puzzle is some function of the branching factor in the first few layers of search below the starting numbers, because this factor indicates how many 'mis-steps' it is possible to make early on.
View user's profile Send private message Send e-mail Back to top
High Poster
Barbie

Joined: 01 Jan 2001
Posts: 713
Location: Reading
Post Posted: Tue Aug 09, 2005 9:14 am Post subject: Reply with quote
Does the "Sudoku Solver" just solve the Sudoku puzzles you're stuck on or can it be used as a computer version to generate new puzzles for me to complete?

L Angel
24 days...
View user's profile Send private message Send e-mail Visit poster's website Back to top
Medium Poster
ballista

Joined: 01 Jan 2001
Posts: 451
Location: London
Post Posted: Tue Aug 09, 2005 9:26 am Post subject: Reply with quote
At the moment it only solves puzzles, but what I'm saying is that it could possibly be adapted to create them. Possibly. By someone who could be bothered. And that may not be me.
View user's profile Send private message Send e-mail Back to top
Medium Poster
ballista

Joined: 01 Jan 2001
Posts: 451
Location: London
Post Posted: Tue Aug 09, 2005 9:29 am Post subject: Reply with quote
Here's my code:


/*
 * Created on 22-Apr-2005
 */
package com.skenepages.sudoku;

import java.io.PrintStream;

/**
 * @author James
 */
public class Sudoku {
     
    private static class Board {
       
        private int [][] board; // rows x columns
       
        public Board() {
           
            board = new int[9][9];
        }
       
        public Board(int [][] board) {
           
            if(board == null || board.length != 9)
                throw new IllegalArgumentException("Board must be 9 x 9");
            for(int y = 0; y < 9; y++)
                if(board[y] == null || board[y].length != 9)
                    throw new IllegalArgumentException("Board must be 9 x 9");
            this.board = board;
        }
       
        public void set(int x, int y, int value) {
           
            board[y][x] = value;
        }
               
        public int get(int x, int y) {
           
            return board[y][x];
        }
               
        public void print(PrintStream out) {
           
            for(int y = 0; y < 9; y++) {

                out.println("+---+---+---+---+---+---+---+---+---+");
                out.print("|");
                for(int x = 0; x < 9; x++)
                    out.print(" " + (board[y][x] > 0? "" + board[y][x] : " ")
                            + " |");
                out.println();
            }
            out.println("+---+---+---+---+---+---+---+---+---+");
        }

        public void printWithHighlight(PrintStream out, int hx, int hy) {
           
            for(int y = 0; y < 9; y++) {

                out.println("+---+---+---+---+---+---+---+---+---+");
                out.print("|");
                for(int x = 0; x < 9; x++)
                    if(y == hy && x == hx)
                       out.print("*" +
                           (board[y][x] > 0? "" + board[y][x] : " ") + "*|");
                    else out.print(" " +
                            (board[y][x] > 0? "" + board[y][x] : " ") + " |");

                out.println();
            }
            out.println("+---+---+---+---+---+---+---+---+---+");
        }
       
        public int [] getRowFreedoms(int y) {
           
            int count = 0;
            boolean [] found = new boolean[9];
            for(int x = 0; x < 9; x++)
                if(board[y][x] > 0) {
                   
                    found[board[y][x] - 1] = true;
                    count++;
                }
            int [] res = new int[9 - count];
            int p = 0;
            for(int x = 0; x < 9; x++)
                if(!found[x]) res[p++] = x + 1;
            return res;
        }
       
        public int [] getColumnFreedoms(int x) {
           
            int count = 0;
            boolean [] found = new boolean[9];
            for(int y = 0; y < 9; y++)
                if(board[y][x] > 0) {
                   
                    found[board[y][x] - 1] = true;
                    count++;
                }
            int [] res = new int[9 - count];
            int p = 0;
            for(int y = 0; y < 9; y++)
                if(!found[y]) res[p++] = y + 1;
            return res;
        }
       
        public int [] getBlockFreedoms(int bx, int by) {
           
            int count = 0;
            boolean [] found = new boolean[9];         
            for(int y = 0; y < 3; y++)
                for(int x = 0; x < 3; x++)
                   if(board[y + by * 3][x + bx * 3] > 0) {
                      
                       found[board[y + by * 3][x + bx * 3] - 1] = true;
                       count++;
                   }
            int [] res = new int[9 - count];
            int p = 0;
            for(int x = 0; x < 9; x++)
                if(!found[x]) res[p++] = x + 1;
            return res;
        }       
    }
   
    /**
     *
     * @param board
     * @return true if solved, false otherwise.
     */
    public static boolean solve(Board board) {
       
        // Find the row, column or block with the least freedoms (but still
        // some work to do)
        int [][] freedoms = new int[27][];
        for(int y = 0; y < 9; y++) freedoms[y] = board.getRowFreedoms(y);
        for(int x = 0; x < 9; x++) freedoms[9 + x] = board.getColumnFreedoms(x);
        for(int by = 0; by < 3; by++)
            for(int bx = 0; bx < 3; bx++)
                freedoms[18 + (by * 3) + bx] =
                    board.getBlockFreedoms(bx, by);
        int count = 9;
        int best = 0;
        boolean solved = true;
        for(int n = 1; n < 27; n++)
            if(freedoms[n].length > 0) {
               
                solved = false;
                if(freedoms[n].length < count) {
                   
                    best = n;
                    count = freedoms[n].length;
                }
            }
        if(solved) return true;
        // For the first free position in the row column or block, assign the
        // first freedom that is acceptable to all intersecting rows, columns
        // and blocks.  If no freedom is acceptable, return.
        if(best < 9) {  // It's a row!
           
            int [] choices = freedoms[best];
            int y = best;           
            for(int x = 0; x < 9; x++) {

                if(board.get(x, y) == 0) {

                   for(int c = 0; c < choices.length; c++) {

                       // Check that choice is acceptable to both column and
                       // block
                       int [] column = freedoms[9 + x];
                       boolean found = false;
                       for(int p = 0; p < column.length  && !found; p++)
                           found |= column[p] == choices[c];
                       if(found) {

                           found = false;
                           int bx = x / 3;
                           int by = y / 3;
                           int [] block = freedoms[18 + by * 3 + bx];
                           for(int p = 0; p < block.length  && !found; p++)
                               found |= block[p] == choices[c];
                           if(found) {

                               board.set(x, y, choices[c]);
                               /*try {
                                  
                                   System.in.read();
                               }
                               catch(IOException iOE) {
                                  
                                   iOE.printStackTrace();
                               }*/
                               board.printWithHighlight(System.out, x, y);
                               if(solve(board)) return true;
                           }
                       }
                   }
                   board.set(x, y, 0);
                   return false;
                }
            }
        }
        else if(best < 18) { // It's a column!

            int [] choices = freedoms[best];
            int x = best - 9;
            for(int y = 0; y < 9; y++) {

                if(board.get(x, y) == 0) {

                   for(int c = 0; c < choices.length; c++) {

                       // Check that choice is acceptable to both row and
                       // block
                       int [] row = freedoms[y];
                       boolean found = false;
                       for(int p = 0; p < row.length  && !found; p++)
                           found |= row[p] == choices[c];
                       if(found) {

                           found = false;
                           int bx = x / 3;
                           int by = y / 3;
                           int [] block = freedoms[18 + by * 3 + bx];
                           for(int p = 0; p < block.length  && !found; p++)
                               found |= block[p] == choices[c];
                           if(found) {

                               board.set(x, y, choices[c]);
                               /*try {
                                  
                                   System.in.read();
                               }
                               catch(IOException iOE) {
                                  
                                   iOE.printStackTrace();
                               }*/
                               board.printWithHighlight(System.out, x, y);
                               if(solve(board)) return true;
                           }
                       }
                   }
                   board.set(x, y, 0);
                   return false;
                }
            }       
        }
        else { // It's a block!
           
            int [] choices = freedoms[best];
            best = best - 18;
            int by = best / 3;
            int bx = best % 3;
            for(int y = 0; y < 3; y++) {
               
                for(int x = 0; x < 3; x++) {

                    if(board.get(bx * 3 + x, by * 3 + y) == 0) {

                       for(int c = 0; c < choices.length; c++) {

                           // Check that choice is acceptable to both row and
                           // column
                           int [] row = freedoms[by * 3 + y];
                           boolean found = false;
                           for(int p = 0; p < row.length  && !found; p++)
                               found |= row[p] == choices[c];
                           if(found) {


                               found = false;
                               int [] column = freedoms[9 + bx * 3 + x];
                               for(int p = 0; p < column.length  && !found; p++)
                                   found |= column[p] == choices[c];
                               if(found) {

                                   board.set(bx * 3 + x, by * 3 + y, choices[c]);
                                   /*try {
                                      
                                       System.in.read();
                                   }
                                   catch(IOException iOE) {
                                      
                                       iOE.printStackTrace();
                                   }*/
                                   board.printWithHighlight(System.out,
                                       bx * 3 + x, by * 3 + y);
                                   if(solve(board)) return true;
                               }
                           }
                       }
                       board.set(bx * 3 + x, by * 3 + y, 0);
                       System.out.println("Backtracking...");
                       board.printWithHighlight(System.out,
                               bx * 3 + x, by * 3 + y);
                       /*try {
                          
                           System.in.read();
                       }
                       catch(IOException iOE) {
                          
                           iOE.printStackTrace();
                       }*/
                       return false;
                    }
                }
            }
        }
        throw new Error("Should never reach this point");
    }
   
    public static void main(String [] args) {
     
        int [][] b = new int[][] {
           
                { 9, 0, 0, 7, 0, 0, 0, 0, 4 },
                { 0, 4, 0, 0, 3, 0, 0, 0, 0 },
                { 0, 0, 2, 0, 0, 6, 0, 3, 0 },
                { 0, 0, 7, 0, 0, 0, 0, 0, 6 },
                { 6, 0, 9, 2, 0, 7, 4, 0, 3 },
                { 8, 0, 0, 0, 0, 0, 7, 0, 0 },
                { 0, 5, 0, 3, 0, 0, 9, 0, 0 },
                { 0, 0, 0, 0, 6, 0, 0, 5, 0 },
                { 2, 0, 0, 0, 0, 5, 0, 0, 1 }
        };
        Board board = new Board(b);
        board.print(System.out);
        if(solve(board)) {
           
            System.out.println("Solution found!");
            board.print(System.out);
        }
        else System.out.println("No solution");
    }
}
View user's profile Send private message Send e-mail Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Tue Aug 09, 2005 9:47 am Post subject: Reply with quote
Posts deleted. Dunno what happened there...

These traditional logic approaches are obviously the best for these sorts of puzzles. In fact this is one of the only puzzles I've come across where the algorithm used by people is almost identical to the one used by programmers (who may or may not be people ). Now I've proved (to myself at least) that Evolutionary Algorithms are not a great way to do Sudoku, I can use them as a test bed for algorithms to avoid local minima.

I may also shamelessly steal your code to use as a comparison

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Tue Aug 09, 2005 10:26 am Post subject: Reply with quote
For what it's worth, here's my code and the application itself. The GUI does make a reasonable paper replacement for playing Sudoku as it removes the need for rubbing out and gives you feedback when you're wrong!

http://logicalgenetics.com/assorted/upload/Sudoku.exe

Enter the fixed numbers on the "Problem" tab and then play around with your solution on the "Solution" tab. Errors are shown in red. Click the "Solve" button to start the Evolutionary Algorithm and watch it almost find the solution

Here's the code - it's not as portable as Skene's, since you need Delphi and upmteen different libraries (all free).

http://logicalgenetics.com/assorted/upload/SudokuDelphi.zip

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Medium Poster
ballista

Joined: 01 Jan 2001
Posts: 451
Location: London
Post Posted: Tue Aug 09, 2005 12:55 pm Post subject: Reply with quote
Please do steal my code, and add it to your GUI. I'd like to see a graphical view of it working, but can't be bothered to do it myself. Although if I did it in Java I could turn it into an applet. Hmm. One day maybe.
View user's profile Send private message Send e-mail Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Fri Aug 19, 2005 12:05 pm Post subject: Reply with quote
So very close!

Made some changes to the Evolutionary Training setup and I think I've almost cracked it. In the picture below the system is trying to solve a sudoku problem with no fixed numbers. That should be easier, because there are far more solutions available than when some cells are fixed. It's not hard to see that it's pretty damned close to having completed the puzzle...

An image

In fact, if it were to swap the two pairs of numbers I've highlighted in the next image, then it'd have done it. The probability of it making those swaps is very low, however, so I guess I just have to wait!

An image

To make the EA work better I took some simple steps to lower the chances of overfitting and premature convergance and also made some problem specific alterations to the fitness function.

To lower the chances of premature convergence I added an age factor into the fitness function - old networks are punished for being old, so regardless of how good they are, all networks will die. Watching the evolution process it was clear that this allowed for the concurrent existance of several distinct solutions in the population.

I also altered the fitness function to give a very large reward to solutions which had one or more "correct" rows, columns or blocks.

DT

PS: As I wrote this post, the solver solved the problem

An image


Last edited by genious on Fri Aug 19, 2005 2:34 pm; edited 1 time in total
View user's profile Send private message Send e-mail Visit poster's website Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Fri Aug 19, 2005 2:17 pm Post subject: Reply with quote
So here where the problem lies...

Over lunch I set the EA solver a simple Sudoku problem from the front page of a popular sudoku site. It's supposed to be an easy problem for "Sudoku Virgins" to solve (I wouldn't know, I consider it below me to solve them in my head ) and the EA solver has done a fairly good job...

An image

And that's the point about EAs: They do a fairly good job. If you look at what the solver's come up with, you might think it looks pretty damned close to correct and you'd be right, because it is, but because with sudoku we want to reach the optimum solution, not just a fairly good one, there's still a lot to be done. There is no obvious set of "swaps" which would allow us to reach the solution, without first messing the board up quite a lot. In order to reach the optimum solution we need to backtrack through several solutions which are less good than the one we have at the moment - either that or wait until a new solution is created having had the exact correct values swapped in the exact correct order; something which is astronomically unlikely.

EA's don't like backtracking. There's only a small chance that an individual which is less fit than the rest of the population will live long enough to breed. If it does manage to breed then there's a similarly low chance that it's offspring will survive, because they will probably have inherited some of the less-good traits of their parent.

So, even though the changes I have made to the EA system have encouraged diversity, they only really allow for the continued existance of diverse sub-populations if the fitness of each of these subpopulations is comperable to the fitness of all other subpopulations.

The next step in making a universal evolutionary Sudoku solver is to work out how to maintain a number of diverse sub-populations and avoid wiping out potentially good solutions just because they aren't good yet.

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Thu Nov 02, 2006 12:08 pm Post subject: Reply with quote
Wrote a very simple depth-first search version of the Sudoku solver for the upcoming Developer's group meeting. It's not as fast as Skene's version, since it doesn't use any heuristics, it just puts symbols in squares using a recursive backtracking approach.

Download the EXE here (set up the problem on the first tab, then watch the solution being found on the second tab).

Starting in the top left, the solver attempts to insert a symbol, then recursively checks subsequent squares, backtracking as appropriate. Like I said, not as clever as Skene's version, which uses a similar algorithm, but speeds up the search by attempting to fill squares with fewer options first (like a human would).

This simple version is a nice demonstration of a standard depth first search, which is the key to my planned talk

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top
Low Poster
J13eauty

Joined: 09 Jan 2009
Posts: 1
Post Posted: Fri Jan 09, 2009 6:21 am Post subject: Please.... Reply with quote
Can you email your program to my email(phoenix_13eauty@yahoo.com)...
i need very much for my task in school about using
EA to solve the sudoku problem...
I need in Visual .NET
can you ?
View user's profile Send private message Back to top
Really High Poster
genious

Joined: 01 Jan 2001
Posts: 3232
Location: Reading, UK
Post Posted: Fri Jan 09, 2009 10:38 am Post subject: Reply with quote
I only have versions of this in Delphi. Sorry!

You can download some code from here...

http://logicalgenetics.com/showarticle.php?topic_id=1624&page_num=2

DT
View user's profile Send private message Send e-mail Visit poster's website Back to top

Logical Genetics Forum Index » Artificial Intelligence » Sudoku Solver


Post new topic     Reply to topic


Display posts from previous:    


Powered by phpBB 2.0.4 © 2001, 2002 phpBB Group