a genetic approach to solving sudoku s

26
Artificial Intelligence: Solving Sudokus using Genetic Algorithms Anthony Woudenberg, Jens van de Water, Leiden University April 28, 2009 1 Abstract This paper is discussing our findings for the fourth and final exercise of Artifical Intellegence, in which we attempt to solve sudokus by using genetic algorithms. We present an analyses of both elitist– and steady state selection, and discuss the pros and cons of either approach. 2 Introduction After the fad several years ago, only few readers still require introduction to the phenomenon “sudoku”. Still, for those few, a quick summary. A sudoku is a fairly popular number puzzle, where the challenge lies in reconstructing a nine by nine grid filled with numbers between one and nine. Contrary to popular belief, the sudoku did not originate in Japan. The puzzle as we came to know it was originally created by the American Howard Garms in 1979 under the name “Numbers in Place”. It got its popularity in the second half of the ’80s, when a Japanese company introduced it under the name Sudoku. It wasn’t until 2005 when the virus spread to the rest of the world. In essence, sudokus are Latin squares with an additional condition. The numbers one to nine are placed on a grid, so that every number appears exactly once in each row, column and block (see Figure 1). A typical sudoku has about 23 to 32 given numbers, the rest is left blank. The amount of givens does have influence on the difficulty of a puzzle, but isn’t the only factor involved. 1

Upload: prashant-soni

Post on 30-Dec-2015

24 views

Category:

Documents


0 download

DESCRIPTION

genetic algo to solve sudoku

TRANSCRIPT

Page 1: A Genetic Approach to Solving Sudoku s

Artificial Intelligence:Solving Sudokus using Genetic Algorithms

Anthony Woudenberg, Jens van de Water, Leiden University

April 28, 2009

1 Abstract

This paper is discussing our findings for the fourth and final exercise ofArtifical Intellegence, in which we attempt to solve sudokus by using geneticalgorithms. We present an analyses of both elitist– and steady state selection,and discuss the pros and cons of either approach.

2 Introduction

After the fad several years ago, only few readers still require introduction tothe phenomenon “sudoku”. Still, for those few, a quick summary.

A sudoku is a fairly popular number puzzle, where the challenge liesin reconstructing a nine by nine grid filled with numbers between one andnine. Contrary to popular belief, the sudoku did not originate in Japan. Thepuzzle as we came to know it was originally created by the American HowardGarms in 1979 under the name “Numbers in Place”. It got its popularity inthe second half of the ’80s, when a Japanese company introduced it underthe name Sudoku. It wasn’t until 2005 when the virus spread to the rest ofthe world.

In essence, sudokus are Latin squares with an additional condition. Thenumbers one to nine are placed on a grid, so that every number appearsexactly once in each row, column and block (see Figure 1). A typical sudokuhas about 23 to 32 given numbers, the rest is left blank. The amount ofgivens does have influence on the difficulty of a puzzle, but isn’t the onlyfactor involved.

1

Page 2: A Genetic Approach to Solving Sudoku s

Figure 1: A normal sudoku,where green represents a col-umn, blue a row, red a blockand yellow a given.

There are several traditional techniques tosolve a sudoku. In this paper however, weuse a rather unique approach by using a ge-netic algorithm (GA). There doesn’t seemto be much material available about solvingsudokus using this method. The only trulyrelevant work we found was by Matere andKoljonen [1]. This means that it’s not re-ally known whether or not GA’s even per-form well when solving sudokus. Since thereare several methods which might solve thepuzzles, we also want to see if one performsbetter. It is our intention to shed a light onthese questions.

3 Method

The main problem with GA’s is mapping your problem in such a way thatthe GA can actually try to solve it. We decided to follow the approach of[1], by translating a sudoku to an array of 81 integers. From block A to I,for position 0 to 8, add the number of that position to the array. This meansthat every group of 9 successive integers is a single block. When starting tosolve the puzzle, the first thing we do is creating an array we called “original”that keeps track of the givens. Considering we never want to change positionsin the sudoku that are already given, every change we are about to make ischecked with this original.

With every GA, it’s crucial that you keep one or more factors constantwhile optimizing the remaining factors. In our implementation, we chose to fillevery block with unique numbers. Because of the way that we mutate anduse recombination, we can guarantee that this uniqueness per block neverchanges. This also means that there are nine occurrences of every number inthe puzzle at any given time.

3.1 Crossover

As mentioned before, the factor that we keep constant is always per block.We want to maintain this property when recombining parents to create new

2

Page 3: A Genetic Approach to Solving Sudoku s

Figure 2: Our three implementations for crossover: the horizontal crossover(A), the vertical crossover (B), and the random crossover (C).

children. For this reason alone, it seems natural to use crossover per block;this means that you always select entire blocks from your parents. Childrenget their blocks in either of three methods (see Figure 2):

1. Horizontal Crossover. A child is randomly assigned rows from its par-ents. (A)

2. Vertical Crossover. Same principle, but with columns. (B)

3. Random Crossover. For every block, a random parent is selected tocopy its corresponding block. (C)

It’s important to note that position of each block doesn’t change when it’scopied to the child. This guarantees that the givens are still at the sameposition, and that only the empty positions are changed.

3.2 Mutation

In contrast to crossover, mutation is always done within a block. Mutationin this context differs a bit from natural mutation; instead of changing thevalue of a position into another random value, we swap multiple positionswithin a block. While the former would create double numbers per block, thelatter keeps our uniqueness intact. Should we decide to mutate, it’s done inone of the following ways (see Figure 3):

1. Two-Swap. Swap the values of two random positions in a random block.If either position is a given, the block and positions are randomly chosenagain. Should this happen too much, consider it a failure and simplyskip it. Has a 50% chance of being performed. (A)

3

Page 4: A Genetic Approach to Solving Sudoku s

Figure 3: Our three mutations, on a block with the value 5 as a given: thetwo-swap (A), the three-swap (B), and the shift (C).

2. Three-Swap. More or less the same as two-swap, but then with threerandom positions. If the three-swap fails after several attempts, weperform a two-swap instead. This is done clockwise or counter clockwisein a 50:50 ratio. Has a 30% chance of being performed. (B)

3. Shift. Randomly select a block, two positions and direction (clockwiseor counter clockwise). These positions should be at least two spacesapart from each other; if this wouldn’t be the case it would be a regularswap. Any givens in this range are barrel shifted, while skipping thegivens. Again, should this fail too much, we try a three-swap. Has a20% chance of being performed. (C)

We already mentioned that not every crossover is followed by a mutation.The chance that we mutate (mutation rate) increases with every generation,using the following formula:

rate = 0.1 + 0.8 ∗ (gen/100, 000)

Where rate is the mutation rate, gen the current generation and 100,000is the cut-off point where we stop the algorithm. This means that rate varieswith linear intervals between 0.1 and 0.9. This decreases the chance that weremain in local extremes, while still working towards acceptable solutions.

3.3 Selection

Given the nature of genetic algorithms, it’s essential to have a sound way toselect the generated children that are most promising to result in a solvedsudoku. To rate the created boards, we rely on three weighted factors:

4

Page 5: A Genetic Approach to Solving Sudoku s

1. Sum: The sum of all numbers in a row. In the optimal case, this wouldbe equal to 1+2+3+ . . .+8+9 = 45. The absolute difference betweenthe optimal and the actual sum is the sumfactor for that row.

2. Product : As with sum, only now with the product of that row. In theoptimal case, the product would be equal to 9!. We take the square rootof the absolute difference between the optimal product and the actualone, thus calculating the productfactor for that row.

3. Completeness : Of the three factors, this one is considered the most im-portant. The completeness of a row is simply the amount of differentnumbers in that particular row. The fitness accompanying the com-pleteness equals nine minus the completeness. In other words, missingnumbers are punished.

Naturally, all of the above is true for columns as well. Keep in mind that inevery block all three of these factors are always zero, since we start out withnine different numbers, and never alter that. When evaluating children, wetherefore only calculate these for the rows and columns. The total fitness fora board is defined as

sumFi =

∣∣∣∣∣∣45−

9∑

j=1

valuei,j

∣∣∣∣∣∣

prdFi =

∣∣∣∣∣∣9!−

9∏

j=1

valuei,j

∣∣∣∣∣∣

cmplFi = |{1, 2, 3, 4, 5, 6, 7, 8, 9} ∩Xi|

F =9∑

r=1

(10 ∗ sumFr + prdFr + 50 ∗ cmplFr)+9∑

c=1

(10 ∗ sumFc + prdFc + 50 ∗ cmplFc)

Here i is either a row or a column, j is an element of that row or column,c stands for column, r for row. F is the total fitness function, while sumF,prdF and complF calculate the sum, product and completeness fitnesses fora specific column or row. Note that the |.| in the cmplF means the cardinality

5

Page 6: A Genetic Approach to Solving Sudoku s

of the set; basicly the amount of missing numbers.Because there was little known about the topic of solving sudokus using

GAs, we decided to incorporate both steady state and elitist selection, to trydeciding on the best through experimenting. We chose for 40 parents, 100children, as this seemed to get reasonable enough results with low calculationtimes.

4 Implementation

We have chosen for the programming language C++ to implement the geneticalgorithm. The input of Sudokus is done using standard input, see [2] for anexample.

5 Results and Conclusions

First and foremost, all discussed results are based on regular nine by nine su-dokus. The results for four by four sudokus were uniform— the GA managedto solve each and every one of them, even completely empty boards. This isbecause the amount of possible states of the board is very small, meaningthe desired state is easily reached. We therefore chose to leave them out ofour tests.

In Figure 4(A) one can see the percentage of solved sudokus with regardto the amount of givens. We started with fully solved sudokus, removinggivens each iteration. Results were of little interest until the point whereless than half of the board was given; before that point every sudoku wassolved in all cases. After this mark, we removed one given per iteration, toget clearer results. Here you can see that elitist selection solved more thanthe steady state method. The reason for this is because steady state seemsto have more problems getting out of local extremes when comparing to theelitist selection.

As Mantere and Koljonen [1] suggested, GA’s can also be used to createthese puzzles. To further test our implementation, we decided to try this outourselves using an empty board as input. The previously found results werefurther emphasized as the steady state managed to create sudokus in a mere5% of our attempts, whereas elitist got over 50%. Again, this could indicatethat elitist selection is more robust to local minima.

6

Page 7: A Genetic Approach to Solving Sudoku s

Figure 4: How often a sudoku gets solved by the two different methods (A),and the results of the sudokus that were solved (B).

It’s important to understand that Figure 4(B) contains results only forthose sudokus that were actually solved. At first glance, steady state seemsto perform better with its lower values. However, because of the fact thatit solves significantly less than elitist selection, results are heavily distorted.Still, it can be argued that the low amount of generations with which steadystate solves the puzzles is admirable. As was to be expected, it takes thealgorithm longer to solve sudokus as they get less givens. This seems toimply that sudokus with less givens are indeed harder to solve.

6 Discussion and Future Work

As promising as this little experiment is, there is an incredible amount of vari-ables that could be tinkered with to further optimize the process. Considerthe following options:

1. Cut-off point. In this test, the point where we gave up all hope ofstill finding a solution was at 100,000. When looking at the results ofmainly the elitist selection, we can’t help but wonder if this was toopremature. Raising this threshold would probably increase the amountof solves puzzles.

2. Amount of children and parents. The values used were not much morethan a wild guess, and seemed to entail reasonable results. Perhaps

7

Page 8: A Genetic Approach to Solving Sudoku s

different values would perform better; more testing would be neededon this account.

3. Mutation Rates. A comparative study would be required to test if ourincreasing mutation rate actually has an better performance. Varyingthe 5:3:2 ratio for the different mutations could also be effective, per-haps even during runtime. It might be sound to use the more radicalshift mutation in the beginning, and start swapping more and more aswe progress.

The biggest concern we encountered through these experiments was the factthat the GA had a hard time coping with local extremes. It goes beyond thescope of this paper, but for future experiments we thought of two possiblesolutions. As it is now, mutation is purely random. It might be smarter toguide this mutation process at the point where no progress is being made,enlarging the chance that you mutate those positions that give the mostconflicts. Another possible solution would be to store all parents at givenintervals. If for some reason no changes are made for countless generations,this approach makes it possible to jump back to a previous state in the hopethat you generate different children, thus escaping the local extremes. Thenagain, this means a significant amount of extra overhead, and might not beworth it in the end.

As a final possible optimization, we have our doubts on the fitness func-tion. It seems to us that the sumFitness and productFitness basically en-tail the exact same information as the completenessFitness. The only addedstrength of either of them is that they are better at quantifying exactly howmuch the desired output differs from the actual output. This on itself raisesa question though; is a nine at the place of a desired one really worse thana two instead of a one? It will significantly speed up the algorithm if it’s de-cided to only take the completeness into consideration, but it would requiremore experimenting to ensure we do not hand in on computational power.

References

[1] T. Mantere, J. Koljonen, “Solving and Rating Sudoku Puzzles with Ge-netic Algorithms”, 2007 IEEE Congress on Evolutionary Computation,CEC2007, pp. 1382-1389

8

Page 9: A Genetic Approach to Solving Sudoku s

[2] W. Kosters, Kunstmatige Intelligentie 2009,http://www.liacs.nl/ kosters/AI/, visited on April 2009

[3] S.J. Russell, P. Norvig, “Artificial intelligence, A modern approach”,sec- ond edition, Prentice Hall, 2003

9

Page 10: A Genetic Approach to Solving Sudoku s

Appendix

main.cpp

// -----------------------------------------------------------------------------

// Name : Anthony Woudenberg

// Jens van der Water

// Student # : 0404659

// 0409871

// Date : April 2009

// Course : Artificial Intelligence

// Assignment : 4- Solving Sudokus using Genetic Algorithm

// Comments : An implementation of a genetic algorithm, to try and solve

// sudokus. Implements both steady state and elitist selection.

// See Readme.txt for guidelines on how to run.

// -----------------------------------------------------------------------------

#include <iostream>

#include "ga.h"

using namespace std;

// ----------------------------------------------------------------------------

// Entrypoint. Commandline parameters:

// 1: inputfile.

// 2: print? (1/0)

// 3: method (1=steady state/0=elitist)

// ----------------------------------------------------------------------------

int main(int argc, char * argv[]) {

CGASudoku sudo(N_CHILDREN, N_PARENTS);

srand(time(NULL));

if(argc>3) { // Read options from commandline

sudo.readFromFile(argv[1]);

if(atoi(argv[3])==0) {

sudo.solveElitair(atoi(argv[2]));

} else {

sudo.solveSteady(atoi(argv[2]));

} // if

} else { // Use some default settings

sudo.readFromFile("Input/Other/3x3toosimple.txt");

sudo.solveElitair(true);

} // if

return 0;

} // main

10

Page 11: A Genetic Approach to Solving Sudoku s

ga.h

#include <iostream>

#include <vector>

#include <string>

#include <fstream>

#include <cstdlib>

#include <ctime>

#include <cmath>

#include <algorithm> // sort()

using namespace std;

// ----------------------------------------------------------------------------

// Options, change at will. Keep chances below 1, and make sure the three swap-

// and shiftrates add up to 1.

// ----------------------------------------------------------------------------

const int N_PARENTS = 40; // # of parents

const int N_CHILDREN = 100; // # of children

const int N_PARENTS_ELITAIR = 10; // # of parents to take to next run

const int MAX_GENERATION = 100000; // # of iterations before we give up

const float MUTATION_RATE_MIN = 0.10; // Minimum mutation rate

const float MUTATION_RATE_MAX = 0.90; // Maximum mutation rate, linear increase

const float SWAPTWO_RATE = 0.50; // If we mutate, chance to 2 swap

const float SWAPTHREE_RATE = 0.30; // " " chance to 3 swap

const float SHIFT_RATE = 0.20; // " " chance to shift

// ----------------------------------------------------------------------------

// Do not change

const float MUTATION_RATE_INCREASE = (MUTATION_RATE_MAX-MUTATION_RATE_MIN)/

MAX_GENERATION;

// ----------------------------------------------------------------------------

// Simple struct to keep track of the sollutions we’re working with. Easier to

// do sort vectors of struct like this, compared to swapping and copying entire

// boards.

// ----------------------------------------------------------------------------

struct CFitness {

CFitness();

int index; // the element this struct is representing

bool isParent; // is that element a parent or a child?

float fitness; // the fitness of that element

bool operator<(const CFitness& rhs) const;

bool operator==(const CFitness& rhs) const;

};

// ----------------------------------------------------------------------------

// A possible sudoku layout.

// ----------------------------------------------------------------------------

class CCandidate {

public:

void create(int size, int original[]);

11

Page 12: A Genetic Approach to Solving Sudoku s

void print();

void copy(const CCandidate & other);

void crossover(CCandidate & parentA, CCandidate & parentB);

// Mutator functions

void swapTwo(int original[]);

void swapThree(int original[]);

void shift(int original[]);

float calcFitness();

bool operator<(const CCandidate& rhs) const;

float fitness;

int puzzle[81];

int puzzleSize;

}; // CCandidate

// ----------------------------------------------------------------------------

// Main solver class.

// ----------------------------------------------------------------------------

class CGASudoku {

public:

CGASudoku(int nChildren, int nParents);

~CGASudoku();

void readFromFile(string szFileName);

void test();

void solveSteady(bool isPrint=true);

void solveElitair(bool isPrint=true);

private:

int original[81];

int puzzleSize; // 4 or 9

vector<CCandidate> parents,

children;

}; // CGASudoku

12

Page 13: A Genetic Approach to Solving Sudoku s

ga.cpp

#include "ga.h"

// ----------------------------------------------------------------------------

// Constructor. Create children and parents.

// ----------------------------------------------------------------------------

CGASudoku::CGASudoku(int nChildren, int nParents) {

int i;

for(i=0; i<nChildren; i++) {

children.push_back(CCandidate());

} // for i

for(i=0; i<nParents; i++) {

parents.push_back(CCandidate());

} // for i

}

// ----------------------------------------------------------------------------

// Destructor. Free memory of the vectors.

// ----------------------------------------------------------------------------

CGASudoku::~CGASudoku() {

children.clear();

parents.clear();

}

// ----------------------------------------------------------------------------

// Read a sudoku from a textfile.

// ----------------------------------------------------------------------------

void CGASudoku::readFromFile(string szFileName) {

fstream file(szFileName.c_str(), ios::in);

int i,j,k,l,

nrt;

if(!file.is_open()) {

cout << "Could not open file." << endl;

exit(0);

} // if

file >> puzzleSize;

nrt = sqrt(puzzleSize);

for(i=0; i<puzzleSize*puzzleSize; i+=nrt*puzzleSize) {

for(j=0; j<puzzleSize; j+=nrt) {

for(k=0; k<puzzleSize*nrt; k+=puzzleSize) {

for(l=0; l<nrt; l++) {

file >> original[i+j+k+l];

} // for l

} // for k

} // for j

} // for i

13

Page 14: A Genetic Approach to Solving Sudoku s

file.close();

} // readFromFile()

// ----------------------------------------------------------------------------

// Try to solve a sudoku using steady state selection.

// ----------------------------------------------------------------------------

void CGASudoku::solveSteady(bool isPrint) {

int i,j,

parentA,

parentB,

generation = 0;

float mutate,

mutationRate = MUTATION_RATE_MIN;

CCandidate parentCopy[N_PARENTS];

bool isDone = false;

vector<CFitness>::iterator endUnique, it;

vector<CFitness> fitnesses;

// Initialise parents; n random parents, givens at the right place

for(i=0; i<N_PARENTS; i++) {

parents[i].create(puzzleSize,original);

parents[i].calcFitness();

} // for

// Initialise fitness placeholder

fitnesses.reserve(N_PARENTS+N_CHILDREN);

for(i=0; i<N_PARENTS+N_CHILDREN; i++) {

fitnesses.push_back(CFitness());

} // for

while(generation<MAX_GENERATION && !isDone) {

generation++;

// copy parent fitnesses to vector

for(i=0; i<N_PARENTS; i++) {

fitnesses[i].fitness = parents[i].fitness;

fitnesses[i].isParent = true;

fitnesses[i].index = i;

} // for

// Create Children

for(i=0; i<N_CHILDREN; i++) {

do {

parentA = rand()%N_PARENTS;

parentB = rand()%N_PARENTS;

} while(parentA==parentB);

mutate = float(rand()%100)/100.0; // will we mutate?

children[i].crossover(parents[parentA],parents[parentB]);

if(mutate <= mutationRate) {

mutate = float(rand()%100)/100.0; // which mutation?

14

Page 15: A Genetic Approach to Solving Sudoku s

if(mutate<=SWAPTWO_RATE) { // Swap two positions

children[i].swapTwo(original);

} else

if(mutate<=SWAPTHREE_RATE+SWAPTWO_RATE) { // Swap three positions

children[i].swapThree(original);

} else { // Shift some random positions

children[i].shift(original);

} // if

} // if

fitnesses[i+N_PARENTS].fitness = children[i].calcFitness();

fitnesses[i+N_PARENTS].isParent = false;

fitnesses[i+N_PARENTS].index = i;

} // for

sort(fitnesses.begin(), fitnesses.end());

// copying the original parents

for(i=0; i<N_PARENTS; i++) {

parentCopy[i] = parents[i];

} // for

// Selection: select the n best UNIQUE boards to be parents

// in the next generation. If there are no n unique boards,

// select all the unique ones, and repeat this cycle to get

// as much diversity in the new generation as possible.

endUnique = unique(fitnesses.begin(), fitnesses.end());

it=fitnesses.begin();

for(i=0; i<N_PARENTS; i++) {

if(fitnesses[i].isParent) {

parents[i] = parentCopy[it->index];

} else {

parents[i] = children[it->index];

} // if

if(it!=endUnique) {

++it;

} else {

it=fitnesses.begin();

} // if

} // for

if(isPrint) {

cout << " generation " << generation << ": ";

for(i=0; i<5; i++) {

cout <<fitnesses[i].fitness << " ";

}

cout << endl;

} // if

// Stop if optimal sollution is reached.

if(parents[0].fitness==0) { isDone=true; }

// Print boards while running

if(isPrint && generation%100==0) {

for(i=0; i<3; i++) {

parents[i].print();

15

Page 16: A Genetic Approach to Solving Sudoku s

}

}

mutationRate+=MUTATION_RATE_INCREASE;

} // while

if(isPrint) { parents[0].print(); }

cout << parents[0].calcFitness() << endl

<< generation << endl;

} // solve()

// ----------------------------------------------------------------------------

// Try to solve a sudoku using elitist selection.

// ----------------------------------------------------------------------------

void CGASudoku::solveElitair(bool isPrint) {

int i,j,

parentA,

parentB,

generation = 0,

nParentsNextGen = 0.25*N_PARENTS;

float mutate,

mutationRate = MUTATION_RATE_MIN;

CCandidate parentCopy[N_PARENTS];

bool isDone = false;

vector<CFitness>::iterator endUnique,

it;

vector<CFitness> fitParents,

fitChildren;

// Initialise parents; n random parents, givens at the right place

for(i=0; i<N_PARENTS; i++) {

parents[i].create(puzzleSize,original);

parents[i].calcFitness();

} // for

// Initialise fitness placeholder

fitParents.reserve(N_PARENTS);

for(i=0; i<N_PARENTS; i++) {

fitParents.push_back(CFitness());

} // for

fitChildren.reserve(N_CHILDREN);

for(i=0; i<N_CHILDREN; i++) {

fitChildren.push_back(CFitness());

} // for

while(generation<MAX_GENERATION && !isDone) {

generation++;

// copy parent fitnesses to vector

for(i=0; i<N_PARENTS; i++) {

fitParents[i].fitness = parents[i].fitness;

fitParents[i].index = i;

} // for

16

Page 17: A Genetic Approach to Solving Sudoku s

// Create Children

for(i=0; i<N_CHILDREN; i++) {

do {

parentA = rand()%N_PARENTS;

parentB = rand()%N_PARENTS;

} while(parentA==parentB);

mutate = float(rand()%100)/100.0; // will we mutate?

children[i].crossover(parents[parentA],parents[parentB]);

if(mutate <= mutationRate) {

mutate = float(rand()%100)/100.0; // which mutation?

if(mutate<=SWAPTWO_RATE) { // Swap two positions

children[i].swapTwo(original);

} else

if(mutate<=SWAPTHREE_RATE+SWAPTWO_RATE) { // Swap three positions

children[i].swapThree(original);

} else { // Shift some random positions

children[i].shift(original);

} // if

} // if

fitChildren[i].fitness = children[i].calcFitness();

fitChildren[i].index = i;

} // for

sort(fitParents.begin(), fitParents.end());

sort(fitChildren.begin(), fitChildren.end());

// copying the original parents

for(i=0; i<N_PARENTS; i++) {

parentCopy[i] = parents[i];

} // for

// copy n best parents

endUnique = unique(fitParents.begin(), fitParents.end());

it=fitParents.begin();

for(i=0; i<nParentsNextGen; i++) {

parents[i] = parentCopy[it->index];

if(it!=endUnique) {

++it;

} else {

it=fitParents.begin();

} // if

} // for

// copy n best children

endUnique = unique(fitChildren.begin(), fitChildren.end());

it=fitChildren.begin();

for(i=0; i<N_PARENTS-nParentsNextGen; i++) {

parents[nParentsNextGen+i] = children[it->index];

if(it!=endUnique) {

++it;

} else {

17

Page 18: A Genetic Approach to Solving Sudoku s

it=fitChildren.begin();

} // if

} // for

sort(parents.begin(), parents.end());

if(isPrint) {

cout << " generation " << generation << ": ";

for(i=0; i<5; i++) {

cout << parents[i].fitness << " ";

}

cout << endl;

} // if

// Stop if optimal sollution is reached.

if(parents[0].fitness==0) { isDone=true; }

// Print boards while running

if(isPrint && generation%1000==0) {

for(i=0; i<3; i++) {

parents[i].print();

}

}

mutationRate+=MUTATION_RATE_INCREASE;

} // while

if(isPrint) { parents[0].print(); }

cout << parents[0].calcFitness() << endl

<< generation << endl;

} // solve()

// ----------------------------------------------------------------------------

// Used for sorting.

// ----------------------------------------------------------------------------

bool CCandidate::operator<(const CCandidate& rhs) const {

return fitness < rhs.fitness;

} // operator<()

18

Page 19: A Genetic Approach to Solving Sudoku s

fitness.cpp

#include "ga.h"

using namespace std;

// ----------------------------------------------------------------------------

// Used for sorting.

// ----------------------------------------------------------------------------

bool CFitness::operator<(const CFitness& rhs) const {

return (fitness < rhs.fitness);

} // operator<

// ----------------------------------------------------------------------------

// Used to determine uniqueness.

// ----------------------------------------------------------------------------

bool CFitness::operator==(const CFitness& rhs) const {

return (fitness == rhs.fitness); // rest doesn’t matter

} // operator<

// ----------------------------------------------------------------------------

// Constructor.

// ----------------------------------------------------------------------------

CFitness::CFitness() {

fitness = 0.0;

isParent = false;

index = 0;

}

19

Page 20: A Genetic Approach to Solving Sudoku s

candidate.cpp

#include "ga.h"

// ----------------------------------------------------------------------------

// Fill the array with random values, while still maintaining

// the original ’givens’. Postcondition is that every number between 1 and 9

// is exactly once in every block.

// ----------------------------------------------------------------------------

void CCandidate::create(int size, int original[]) {

int value,block,square, position;

bool valueFound;

puzzleSize = size;

// copy

for(position=0; position<puzzleSize*puzzleSize; position++) {

puzzle[position] = original[position];

} // for

for(block=0; block<puzzleSize*puzzleSize; block+=puzzleSize) {

for(value=1; value<=puzzleSize; value++) {

valueFound = false;

for(square=0; square<puzzleSize; square++) {

if(value==puzzle[block+square]) valueFound = true;

} // for

if(!valueFound) {

position = rand()%puzzleSize;

while(puzzle[block+position]!=0) {

position = (position+1)%puzzleSize;

} // while

puzzle[block+position] = value;

} // for square

} // for value

} // for block

} // create()

// ----------------------------------------------------------------------------

// Calculate and store the fitness for this board, and return that fitnessvalue

// back. Calculated for each row and column, consisting of the sum, product and

// completeness as compared to the optimal values. Higher values means the

// sollution is less optimal.

//

// Could use some optimization, but is more readable like this.

// ----------------------------------------------------------------------------

float CCandidate::calcFitness() {

int sumFitness = 0,

complFitness = 0,

i,j,k,l,

nrt = sqrt(puzzleSize), // "n root"

sum,

prod,

completeness,

seen[9],

optprod = (puzzleSize==4?24:362880), // =n!

20

Page 21: A Genetic Approach to Solving Sudoku s

optsum = (puzzleSize==4?10:45);

float prodFitness = 0.0;

// per row

for(i=0; i<puzzleSize*puzzleSize; i+=nrt*puzzleSize) {

for(j=0; j<puzzleSize; j+=nrt) {

sum = 0;

prod = 1;

completeness = 0;

for(k=0; k<puzzleSize; k++) {

seen[k] = false;

} // for k

for(k=0; k<puzzleSize*nrt; k+=puzzleSize) {

for(l=0; l<nrt; l++) {

sum+=puzzle[i+j+k+l];

prod*=puzzle[i+j+k+l];

seen[puzzle[i+j+k+l]-1] = true; // track occurrences of values

} // for l

} // for k

for(k=0; k<puzzleSize; k++) {

if(seen[k]) completeness++; // count number of different numbers

} // for k

sumFitness += abs(optsum-sum);

prodFitness += sqrt(abs(optprod-prod));

complFitness += puzzleSize-completeness;

} // for j

} // for i

// per column

for(k=0; k<puzzleSize*nrt; k+=puzzleSize) {

for(l=0; l<nrt; l++) {

sum = 0;

prod = 1;

completeness = 0;

for(i=0; i<puzzleSize; i++) {

seen[i] = false;

} // for k

for(i=0; i<puzzleSize*puzzleSize; i+=nrt*puzzleSize) {

for(j=0; j<puzzleSize; j+=nrt) {

sum+=puzzle[i+j+k+l];

prod*=puzzle[i+j+k+l];

seen[puzzle[i+j+k+l]-1] = true;

} // for j

} // for i

for(i=0; i<puzzleSize; i++) {

if(seen[i]) completeness++;

} // for i

sumFitness += abs(optsum-sum);

21

Page 22: A Genetic Approach to Solving Sudoku s

prodFitness += sqrt(abs(optprod-prod));

complFitness += puzzleSize-completeness;

} // for l

} // for k

fitness = 10.0*sumFitness + prodFitness + 50.0*complFitness;

return fitness;

} // calcFitness()

// ----------------------------------------------------------------------------

// Swap two random values in a single block, on the condition they’re not

// givens. As long as you accidentily picked a given to switch with, keep on

// trying for a while until we do find nice values, choosing random blocks

// while we’re at it. Pure random, so no guiding whatsoever.

// ----------------------------------------------------------------------------

void CCandidate::swapTwo(int original[]) {

int block = rand()%puzzleSize,

posA = rand()%puzzleSize,

posB = rand()%puzzleSize,

temp,

nAttempts = 0;

while((posA==posB || original[block*puzzleSize+posA] ||

original[block*puzzleSize+posB]) &&

nAttempts<puzzleSize*puzzleSize) {

block = rand()%puzzleSize;

posA = rand()%puzzleSize;

posB = rand()%puzzleSize;

nAttempts++;

} // while

if(nAttempts>=puzzleSize*puzzleSize) { // give up after n attempts

//cout << "--- Given up on 2 swap!" << endl;

return;

}

temp = puzzle[block*puzzleSize+posA];

puzzle[block*puzzleSize+posA] = puzzle[block*puzzleSize+posB];

puzzle[block*puzzleSize+posB] = temp;

} // swapTwo

// ----------------------------------------------------------------------------

// As twoswap, but with three values. Constraints of givens remaining the same

// still holds. As the three chosen positions are purely random (within a

// block), swapping can be done either clockwise or counter clockwise.

// ----------------------------------------------------------------------------

void CCandidate::swapThree(int original[]) {

int block = rand()%puzzleSize,

posA = rand()%puzzleSize,

posB = rand()%puzzleSize,

posC = rand()%puzzleSize,

nAttempts = 0,

temp,

i;

22

Page 23: A Genetic Approach to Solving Sudoku s

while((posA==posB || posA==posC || posB==posC ||

original[block*puzzleSize+posA] ||

original[block*puzzleSize+posB] ||

original[block*puzzleSize+posC]) &&

nAttempts<puzzleSize*puzzleSize) {

block = rand()%puzzleSize;

posA = rand()%puzzleSize;

posB = rand()%puzzleSize;

posC = rand()%puzzleSize;

nAttempts++;

} // while

if(nAttempts>=puzzleSize*puzzleSize) {

swapTwo(original);

return;

} // if

temp = puzzle[block*puzzleSize+posA];

puzzle[block*puzzleSize+posA] = puzzle[block*puzzleSize+posB];

puzzle[block*puzzleSize+posB] = puzzle[block*puzzleSize+posC];

puzzle[block*puzzleSize+posC] = temp;

} // swapThree

// ----------------------------------------------------------------------------

// Barrelshift values within a block between two nongiven values. Can be done

// either clockwise or counter clockwise. Two borders between which we shift

// are wide enough apart from eachother to make sure it’s a shift, and not a

// romantised swap. Any givens inside the range are jumped over:

//

// A B C 5 D E ---> E A B 5 C D

//

// Where 5 is the given, A-E are nongivens.

// ----------------------------------------------------------------------------

void CCandidate::shift(int original[]) {

int block = rand()%puzzleSize,

posA = rand()%puzzleSize,

posB = rand()%puzzleSize,

direction = rand()%2,

nAttempts = 0,

temp,

i;

if(direction) { // clockwise

while((posB-posA<=2 || original[block*puzzleSize+posA]) &&

nAttempts<puzzleSize*puzzleSize) {

block = rand()%puzzleSize;

posA = rand()%puzzleSize;

posB = rand()%puzzleSize;

nAttempts++;

} // while

// give up after n attempts, and do 3 swap

if(nAttempts>=puzzleSize*puzzleSize) {

swapThree(original);

return;

} // if

23

Page 24: A Genetic Approach to Solving Sudoku s

for(i=posA+1; i<=posB; i++) {

if(!original[block*puzzleSize+i]) {

temp = puzzle[block*puzzleSize+posA];

puzzle[block*puzzleSize+posA] = puzzle[block*puzzleSize+i];

puzzle[block*puzzleSize+i] = temp;

} // if

} // for

} else { // counterclockwise

while((posB-posA<=2 || original[block*puzzleSize+posB]) &&

nAttempts<puzzleSize*puzzleSize) {

block = rand()%puzzleSize;

posA = rand()%puzzleSize;

posB = rand()%puzzleSize;

nAttempts++;

} // while

if(nAttempts>=puzzleSize*puzzleSize) {

swapThree(original);

return;

} // if

for(i=posB-1; i>=posA; i--) {

if(!original[block*puzzleSize+i]) {

temp = puzzle[block*puzzleSize+posB];

puzzle[block*puzzleSize+posB] = puzzle[block*puzzleSize+i];

puzzle[block*puzzleSize+i] = temp;

} // if

} // for

} // if

} // Shift()

// ----------------------------------------------------------------------------

// Crossover two parents to create a non-mutated child. Crossover can be done

// in either of three methods, at random:

// * per row: copy entire rows of blocks from parents

// * per column: copy entire columns " "

// * random: copy each block from a random parent.

// ----------------------------------------------------------------------------

void CCandidate::crossover(CCandidate & parentA, CCandidate & parentB) {

int i, j,

nrt,

method=rand()%3,

offset;

bool parent;

puzzleSize = parentA.puzzleSize;

nrt = sqrt(puzzleSize);

switch(method) {

case 0: // pure random crossover

for(i=0; i<puzzleSize*puzzleSize; i+=puzzleSize) {

parent = rand()%2;

for(j=0; j<puzzleSize; j++) {

if(parent) {

puzzle[i+j] = parentA.puzzle[i+j];

24

Page 25: A Genetic Approach to Solving Sudoku s

} else {

puzzle[i+j] = parentB.puzzle[i+j];

} // if

} // for

} // for

break;

case 1: // crossover per row

offset = 1+rand()%(nrt-1);

for(i=0; i<offset*puzzleSize*nrt; i++) {

puzzle[i] = parentA.puzzle[i];

}

for(i=offset*puzzleSize*nrt; i<puzzleSize*puzzleSize; i++) {

puzzle[i] = parentB.puzzle[i];

}

break;

case 2: // crossover per column

offset = 1+rand()%(nrt-1);

for(i=0; i<puzzleSize*puzzleSize; i+=puzzleSize) {

for(j=0; j<offset*puzzleSize; j++) {

puzzle[i+j] = parentA.puzzle[i+j];

} // for j

for(j=offset*puzzleSize; j<nrt*puzzleSize; j++) {

puzzle[i+j] = parentB.puzzle[i+j];

} // for j

} // for i

break;

default:

cout << "This should never happen." << endl;

break;

} // switch

} // crossover()

// ----------------------------------------------------------------------------

// Print a board out to standard output.

// ----------------------------------------------------------------------------

void CCandidate::print() {

int i,j, k, l, nrt = sqrt(puzzleSize);

for(i=0; i<puzzleSize*puzzleSize; i+=nrt*puzzleSize) {

for(j=0; j<puzzleSize; j+=nrt) {

for(k=0; k<puzzleSize*nrt; k+=puzzleSize) {

for(l=0; l<nrt; l++) {

cout << puzzle[i+j+k+l] << " ";

} // for l

if(k<(puzzleSize*nrt)-puzzleSize) { cout << "| "; }

} // for k

cout << endl;

} // for j

if(i<(puzzleSize*puzzleSize)-(nrt*puzzleSize)) {

for(j=0; j<puzzleSize+nrt-1; j++) {

cout << "- ";

25

Page 26: A Genetic Approach to Solving Sudoku s

} // for

cout << endl;

} // if

} // for i

cout << endl;

} // print()

// ----------------------------------------------------------------------------

// Copy one board to the other.

// ----------------------------------------------------------------------------

void CCandidate::copy(const CCandidate & other) {

int i;

puzzleSize = other.puzzleSize;

for(i=0; i<puzzleSize*puzzleSize; i++) {

puzzle[i] = other.puzzle[i];

} // for

} // copy()

26