From 26d454c97618ea8c29c76ac0bcd1dc2879eabc0f Mon Sep 17 00:00:00 2001 From: emil Date: Sun, 1 Dec 2024 18:04:27 +0300 Subject: [PATCH] Selected some arguments for new method --- Main.java | 18 +- other/.LCKMain.java~ | 1 + other/Main.java | 368 ++++++++++++++++++ .../demo/src/main/java/com/example/Main.java | 82 ++-- .../classes/com/example/Main$Chromosome.class | Bin 3549 -> 3549 bytes .../target/classes/com/example/Main.class | Bin 8853 -> 9577 bytes 6 files changed, 438 insertions(+), 31 deletions(-) create mode 100644 other/.LCKMain.java~ create mode 100644 other/Main.java diff --git a/Main.java b/Main.java index 8fc9910..398bfe4 100644 --- a/Main.java +++ b/Main.java @@ -51,20 +51,22 @@ public class Main { // Choosing variables for different sudoku difficulties if (mutablePositions.size() < EASYTHRESHOLD) { // Easy sudoku - POPULATIONSIZE = 100; - TOURNAMENTSIZE = 5; - MUTATIONRATE = 0.05; + POPULATIONSIZE = 100000; + TOURNAMENTSIZE = 8; + MUTATIONRATE = 0.34; } else if (mutablePositions.size() < HARDTHRESHOLD) { // Hard sudoku - POPULATIONSIZE = 100; - TOURNAMENTSIZE = 5; - MUTATIONRATE = 0.057; + POPULATIONSIZE = 100000; + TOURNAMENTSIZE = 8; + MUTATIONRATE = 0.34; } else { // Ultra-hard sudoku - POPULATIONSIZE = 20000; + POPULATIONSIZE = 250000; TOURNAMENTSIZE = 4; - MUTATIONRATE = 0.032; + MUTATIONRATE = 0.15; } + + // Generate initial population of 100 chromosomes mainInstance.generateInitialChromosomes(POPULATIONSIZE, baseSudoku, mutablePositions); diff --git a/other/.LCKMain.java~ b/other/.LCKMain.java~ new file mode 100644 index 0000000..a273035 --- /dev/null +++ b/other/.LCKMain.java~ @@ -0,0 +1 @@ +/home/emil/Coding/Assignments/ITAI/Sudoku_solver/other/Main.java \ No newline at end of file diff --git a/other/Main.java b/other/Main.java new file mode 100644 index 0000000..06e48f9 --- /dev/null +++ b/other/Main.java @@ -0,0 +1,368 @@ +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Main { + // Initalizing variables + public static int POPULATIONSIZE = 0; + public static int TOURNAMENTSIZE = 0; + public static double MUTATIONRATE = 0; + // Threshold of number of mutable positions for easy level sudoku + public static int EASYTHRESHOLD = 60; + public static int HARDTHRESHOLD = 70; + // List to store the population of chromosomes + List population = new ArrayList<>(); + Random random = new Random(); + + public static void main(String[] args) { + // Base Sudoku matrix (input matrix) + int[][] baseSudoku = new int[9][9]; + // List to track positions in Sudoku that are mutable + List mutablePositions = new ArrayList<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + + try { + // Reading the Sudoku matrix from the console input + for (int i = 0; i < 9; i++) { + // Split the input by space + String[] tokens = reader.readLine().split(" "); + for (int j = 0; j < 9; j++) { + if (tokens[j].equals("-")) { + // Empty cells are marked as 0 + baseSudoku[i][j] = 0; + // Add mutable positions (i, j) to the list + mutablePositions.add(new int[]{i, j}); + } else { + // Set fixed value from the input + baseSudoku[i][j] = Integer.parseInt(tokens[j]); + } + } + } + } catch (IOException e) { + // If an error occurs during input reading, print the error and stop the program + System.err.println("Error reading input: " + e.getMessage()); + return; + } + + Main mainInstance = new Main(); // Create instance of Main class + + // Choosing variables for different sudoku difficulties + if (mutablePositions.size() < EASYTHRESHOLD) { // Easy sudoku + POPULATIONSIZE = 100; + TOURNAMENTSIZE = 5; + MUTATIONRATE = 0.05; + } else if (mutablePositions.size() < HARDTHRESHOLD) { // Hard sudoku + POPULATIONSIZE = 100; + TOURNAMENTSIZE = 5; + MUTATIONRATE = 0.057; + } + else { // Ultra-hard sudoku + POPULATIONSIZE = 20000; + TOURNAMENTSIZE = 4; + MUTATIONRATE = 0.032; + } + + // Generate initial population of 100 chromosomes + mainInstance.generateInitialChromosomes(POPULATIONSIZE, baseSudoku, mutablePositions); + + Chromosome bestSolution = null; + + int generation = 0; // Track the number of generations + while (true) { + // Evaluate the fitness of each chromosome in the population + mainInstance.evaluatePopulation(); + + List newPopulation = new ArrayList<>(); + for (int i = 0; i < mainInstance.population.size() / 2; i++) { + // Select parents using tournament selection + List parents = mainInstance.tournamentSelection(TOURNAMENTSIZE); + // Perform crossover to create two children from the selected parents + Chromosome child1 = mainInstance.crossoverBySubgrids(parents.get(0), parents.get(1)); + Chromosome child2 = mainInstance.crossoverBySubgrids(parents.get(1), parents.get(0)); + + // Apply mutation to both children + mainInstance.mutateChromosome(child1, MUTATIONRATE); + mainInstance.mutateChromosome(child2, MUTATIONRATE); + + // Add both children to the new population + newPopulation.add(child1); + newPopulation.add(child2); + } + + // Replace the old population with the new population + mainInstance.population = newPopulation; + + // Get the best chromosome from the current population + bestSolution = mainInstance.getBestChromosome(); + generation++; + + // If the best solution found has a fitness of 0, print it and end the program + if (bestSolution.getFitness() == 0) { + bestSolution.printChromosome(false); + return; + } + } + } + + // Generate initial population of chromosomes + public void generateInitialChromosomes(int numberOfChromosomes, int[][] baseSudoku, List mutablePositions) { + for (int i = 0; i < numberOfChromosomes; i++) { + // Create a copy of the base Sudoku + int[][] sudoku = copyMatrix(baseSudoku); + // Randomly fill mutable positions + for (int[] pos : mutablePositions) { + int row = pos[0]; + int col = pos[1]; + sudoku[row][col] = random.nextInt(9) + 1; + } + // Create a new chromosome with the generated Sudoku and mutable positions + Chromosome chromosome = new Chromosome(sudoku, new ArrayList<>(mutablePositions)); + chromosome.evaluateFitness(); // Evaluate its fitness + population.add(chromosome); // Add to the population + } + } + + // Create a deep copy of a matrix + private int[][] copyMatrix(int[][] original) { + int[][] copy = new int[original.length][original[0].length]; + for (int i = 0; i < original.length; i++) { + System.arraycopy(original[i], 0, copy[i], 0, original[i].length); + } + return copy; + } + + + public List tournamentSelection(int tournamentSize) { + List selectedParents = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + List tournament = new ArrayList<>(); + // Randomly select chromosomes for the tournament + for (int j = 0; j < tournamentSize; j++) { + Chromosome randomChromosome = population.get(random.nextInt(population.size())); + tournament.add(randomChromosome); + } + // Determine the best chromosome in the tournament based on fitness + Chromosome best = tournament.get(0); + for (Chromosome chromosome : tournament) { + if (chromosome.getFitness() < best.getFitness()) { + best = chromosome; + } + } + // Add the best chromosome to the list of selected parents + selectedParents.add(best); + } + return selectedParents; + } + + public Chromosome crossoverBySubgrids(Chromosome parent1, Chromosome parent2) { + int[][] childSudoku = new int[9][9]; + + // Copy the entire Sudoku grid from parent1 to the child + for (int row = 0; row < 9; row++) { + System.arraycopy(parent1.getSudoku()[row], 0, childSudoku[row], 0, 9); + } + + // Determine the number of subgrids to swap from parent2 to child (1-5) + int numSubgridsToSwap = random.nextInt(5) + 1; + List selectedSubgrids = new ArrayList<>(); + while (selectedSubgrids.size() < numSubgridsToSwap) { + int subgridIndex = random.nextInt(9); + if (!selectedSubgrids.contains(subgridIndex)) { + selectedSubgrids.add(subgridIndex); + } + } + + // Swap the selected subgrids from parent2 into the child + for (int subgrid : selectedSubgrids) { + int rowStart = (subgrid / 3) * 3; + int colStart = (subgrid % 3) * 3; + for (int row = rowStart; row < rowStart + 3; row++) { + for (int col = colStart; col < colStart + 3; col++) { + childSudoku[row][col] = parent2.getSudoku()[row][col]; + } + } + } + + // Create a new chromosome with the resulting child Sudoku and evaluate its fitness + Chromosome child = new Chromosome(childSudoku, parent1.getMutablePositions()); + child.evaluateFitness(); + return child; + } + + public void printPopulation(boolean printMutPos) { + System.out.println("Generated Population:"); + int count = 1; + // Print each chromosome's Sudoku and fitness value + for (Chromosome chromosome : population) { + System.out.println("Chromosome " + count + ":"); + chromosome.printChromosome(printMutPos); + System.out.println("Fitness: " + chromosome.getFitness()); + System.out.println(); + count++; + } + } + + public void mutateChromosome(Chromosome chromosome, double mutationRate) { + int[][] sudoku = chromosome.getSudoku(); + List mutablePositions = chromosome.getMutablePositions(); + + // Mutate each mutable position with a probability defined by mutationRate + for (int[] pos : mutablePositions) { + if (random.nextDouble() < mutationRate) { + int row = pos[0]; + int col = pos[1]; + int newValue = random.nextInt(9) + 1; // Assign a new value between 1 and 9 + sudoku[row][col] = newValue; + } + } + + // Update the chromosome's Sudoku and recalculate its fitness + chromosome.setSudoku(sudoku); + chromosome.evaluateFitness(); + } + + public void evaluatePopulation() { + // Evaluate the fitness of each chromosome in the population + for (Chromosome chromosome : population) { + chromosome.evaluateFitness(); + } + } + + public Chromosome getBestChromosome() { + // Find and return the chromosome with the best (lowest) fitness in the population + Chromosome best = population.get(0); + for (Chromosome chromosome : population) { + if (chromosome.getFitness() < best.getFitness()) { + best = chromosome; + } + } + return best; + } + + // Chromosome class representing an individual solution + public class Chromosome { + private int[][] sudoku; // Sudoku grid representing the chromosome + private List mutablePositions; // Positions that can be changed (mutable) + private int fitness; // Fitness value representing the number of conflicts + + // Constructor for initializing a Chromosome with a Sudoku grid and mutable positions + public Chromosome(int[][] sudoku, List mutablePositions) { + this.sudoku = sudoku; + this.mutablePositions = mutablePositions; + } + + public int[][] getSudoku() { + return sudoku; + } + + public void setSudoku(int[][] sudoku) { + this.sudoku = sudoku; + } + + public List getMutablePositions() { + return mutablePositions; + } + + public int getFitness() { + return fitness; + } + + // Evaluate the fitness of the Sudoku by counting the number of row, column, and subgrid violations + public void evaluateFitness() { + fitness = countRowViolations() + countColumnViolations() + countSubgridViolations(); + } + + private int countRowViolations() { + int violations = 0; + // Iterate through each row to count conflicts + for (int i = 0; i < 9; i++) { + boolean[] present = new boolean[10]; + for (int j = 0; j < 9; j++) { + int value = sudoku[i][j]; + if (value != 0) { + if (present[value]) { + violations++; // Increment violations if the value is already seen + } else { + present[value] = true; // Mark the value as seen + } + } + } + } + return violations; + } + private int countColumnViolations() { + int violations = 0; + // Loop through each column + for (int j = 0; j < 9; j++) { + boolean[] present = new boolean[10]; // Track numbers present in the column + for (int i = 0; i < 9; i++) { + int value = sudoku[i][j]; + if (value != 0) { + // If the number is already present, increment the violations count + if (present[value]) { + violations++; + } else { + // Mark the number as present + present[value] = true; + } + } + } + } + return violations; + } + + private int countSubgridViolations() { + int violations = 0; + // Loop through each 3x3 subgrid + for (int gridRow = 0; gridRow < 3; gridRow++) { + for (int gridCol = 0; gridCol < 3; gridCol++) { + boolean[] present = new boolean[10]; // Track numbers present in the subgrid + // Loop through cells in the 3x3 subgrid + for (int row = gridRow * 3; row < gridRow * 3 + 3; row++) { + for (int col = gridCol * 3; col < gridCol * 3 + 3; col++) { + int value = sudoku[row][col]; + if (value != 0) { + // If the number is already present, increment the violations count + if (present[value]) { + violations++; + } else { + // Mark the number as present + present[value] = true; + } + } + } + } + } + } + return violations; + } + + public void printChromosome(boolean printMutPos) { + // Print the Sudoku matrix + for (int[] row : sudoku) { + for (int j = 0; j < row.length; j++) { + System.out.print(row[j]); + if (j < row.length - 1) { + System.out.print(" "); + } + } + System.out.println(); + } + // If requested, print mutable positions + if (printMutPos) { + System.out.println("Mutable Positions:"); + for (int i = 0; i < mutablePositions.size(); i++) { + int[] pos = mutablePositions.get(i); + System.out.print("(" + pos[0] + ", " + pos[1] + ")"); + if (i < mutablePositions.size() - 1) { + System.out.print(" "); + } + } + System.out.println(); + } + } + } +} diff --git a/statistical/demo/src/main/java/com/example/Main.java b/statistical/demo/src/main/java/com/example/Main.java index 40dc4d8..c1d0c74 100644 --- a/statistical/demo/src/main/java/com/example/Main.java +++ b/statistical/demo/src/main/java/com/example/Main.java @@ -57,18 +57,18 @@ public class Main { // Choosing variables for different sudoku difficulties if (mutablePositions.size() < EASYTHRESHOLD) { // Easy sudoku - POPULATIONSIZE = 75; - TOURNAMENTSIZE = 4; - MUTATIONRATE = 0.04; + POPULATIONSIZE = 500000; + TOURNAMENTSIZE = 3; + MUTATIONRATE = 0.1; } else if (mutablePositions.size() < HARDTHRESHOLD) { // Hard sudoku - POPULATIONSIZE = 150; - TOURNAMENTSIZE = 5; - MUTATIONRATE = 0.055; + POPULATIONSIZE = 100000; + TOURNAMENTSIZE = 8; + MUTATIONRATE = 0.34; } else { // Ultra-hard sudoku - POPULATIONSIZE = 20000; - TOURNAMENTSIZE = 4; - MUTATIONRATE = 0.032; + POPULATIONSIZE = 500000; + TOURNAMENTSIZE = 3; + MUTATIONRATE = 0.1; } // Generate initial population of chromosomes @@ -109,7 +109,7 @@ public class Main { generation++; // Plot the fitness graph every 10 generations - if (generation % 100 == 0) { + if (generation % 1 == 0) { mainInstance.plotFitness(fitnessValues); } @@ -126,11 +126,32 @@ public class Main { for (int i = 0; i < numberOfChromosomes; i++) { // Create a copy of the base Sudoku int[][] sudoku = copyMatrix(baseSudoku); - // Randomly fill mutable positions - for (int[] pos : mutablePositions) { - int row = pos[0]; - int col = pos[1]; - sudoku[row][col] = random.nextInt(9) + 1; + // Randomly fill mutable positions while ensuring no duplicates in subgrids + for (int gridRow = 0; gridRow < 3; gridRow++) { + for (int gridCol = 0; gridCol < 3; gridCol++) { + boolean[] present = new boolean[10]; + List subgridPositions = new ArrayList<>(); + // Collect all positions in the current 3x3 subgrid + for (int row = gridRow * 3; row < gridRow * 3 + 3; row++) { + for (int col = gridCol * 3; col < gridCol * 3 + 3; col++) { + int value = sudoku[row][col]; + if (value != 0) { + present[value] = true; + } else { + subgridPositions.add(new int[]{row, col}); + } + } + } + // Randomly fill the subgrid ensuring no duplicates + for (int[] pos : subgridPositions) { + int newValue; + do { + newValue = random.nextInt(9) + 1; + } while (present[newValue]); + sudoku[pos[0]][pos[1]] = newValue; + present[newValue] = true; + } + } } // Create a new chromosome with the generated Sudoku and mutable positions Chromosome chromosome = new Chromosome(sudoku, new ArrayList<>(mutablePositions)); @@ -220,15 +241,29 @@ public class Main { public void mutateChromosome(Chromosome chromosome, double mutationRate) { int[][] sudoku = chromosome.getSudoku(); - List mutablePositions = chromosome.getMutablePositions(); - // Mutate each mutable position with a probability defined by mutationRate - for (int[] pos : mutablePositions) { - if (random.nextDouble() < mutationRate) { - int row = pos[0]; - int col = pos[1]; - int newValue = random.nextInt(9) + 1; // Assign a new value between 1 and 9 - sudoku[row][col] = newValue; + // With a probability defined by mutationRate, perform a mutation by swapping subgrids + if (random.nextDouble() < mutationRate) { + // Randomly select two different subgrids to swap + int subgrid1, subgrid2; + do { + subgrid1 = random.nextInt(9); + subgrid2 = random.nextInt(9); + } while (subgrid1 == subgrid2); + + // Get the starting coordinates for both subgrids + int rowStart1 = (subgrid1 / 3) * 3; + int colStart1 = (subgrid1 % 3) * 3; + int rowStart2 = (subgrid2 / 3) * 3; + int colStart2 = (subgrid2 % 3) * 3; + + // Swap the values in the two selected subgrids + for (int rowOffset = 0; rowOffset < 3; rowOffset++) { + for (int colOffset = 0; colOffset < 3; colOffset++) { + int temp = sudoku[rowStart1 + rowOffset][colStart1 + colOffset]; + sudoku[rowStart1 + rowOffset][colStart1 + colOffset] = sudoku[rowStart2 + rowOffset][colStart2 + colOffset]; + sudoku[rowStart2 + rowOffset][colStart2 + colOffset] = temp; + } } } @@ -236,6 +271,7 @@ public class Main { chromosome.setSudoku(sudoku); chromosome.evaluateFitness(); } + public void evaluatePopulation() { // Evaluate the fitness of each chromosome in the population diff --git a/statistical/demo/target/classes/com/example/Main$Chromosome.class b/statistical/demo/target/classes/com/example/Main$Chromosome.class index ad09172fa3b8a9f95e4057ed8a760a6cadf1acd0..815de1c9a1695fdfcaffdc8d7ea065f061ca795a 100644 GIT binary patch delta 300 zcmWN|!7IaY9LDkIQ9s|`FbABhDNcyZuMxGeRu&CEETv|f87VnvWW zD}RWi)BX|{&*`bp=l$x2x}i?k1ZNRo1cQuXm~(d8lo_+nX4zSU!zKG;e8%mVn_$u& zxEW^^LrmK*_t9HN4>K^_zyLRO-9nf?Cb*3$?qG(yh%$?L{Z`biao-mD#%(Ro@p6)R z$%152@=&rQS(ZGOJdr$=EK24j&n5p-7g80eOQ|cVYmBlgb%O|R5#t@=yqBmU#RsV> z_S77yKa;Dl@%`DKpx49*TbSe%7PLveAk8Lxwvpv4aO|7#UG&W(-!CPMSdoMH5X@VYIM?T|m3gvPHCw7V%&8 z6ke03dByBpY1MmBLM&8I9X6&R3moZE*juaE7U|(|5yxUt=3KrAm#|}AdW}SeZ zId}G~IZQEYzML0-0b^W*%_St5(>0F_3z+9J@+_jr6|Ay^HT~-9S}dE3Sk^qdLw`fE zBDpD9m8?kyl6A?3Ng?n@m=g;Ix7N0?zGb&L|*DDwn0o=Tjd$&OTn zwwkW`8@UH|zdz#v{T|Z1LXOv1*EV>IUG@;NkB9?wc&GFpui7_0;)73SJo)<%jDauX diff --git a/statistical/demo/target/classes/com/example/Main.class b/statistical/demo/target/classes/com/example/Main.class index b63c52e42a7599e6a0fbb79cd18f233fb350d2ba..9d0c4b8c36fbfe0023230507b80c8e24eaf786dd 100644 GIT binary patch delta 3502 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 M)a5Sf75durZ%Mk{jsO4v delta 2858 zcmZ8j3sh9s72Rj%&6kG^$S{rw3`!IRMj|TK@q>z=qJp+03Vx!1BcM1#U{K>HW363n zVp?lonq_S2Qj;vxHnmlo2F?@+cee0Xqwbtr29Q4$SN$}z3<+0 z&)MhPv-jhpnLnQ9eEsf`V*n;d`vK=oCDU5=+`9K?>`z~BEqgKl^OM^a6O)J2;k;b0m>JStjb04?$> z3TCgUTU}S&QWtJ4u8y`fha1PG!6Y6hB9f__wd6+#M zT4R9FEd2sXprcur0wax7b%b>uPa89#a8L!HOuk31j z?Q{=is!Wo}+9&;%I?835O{NM0+L;uu)|!%Go-Q-A$5O^wD`b{UW@@jeWSeJ8rFJ=G zh0M|Bxdz*-k_H6$x^K$wNUD{ZK=DIhwKv4Mx<5 z9glRvyALHrU0{nGMAlwpq~a-V+l}q@#&-YIDqru3jhgB{=XNb?-3g~x@mk|})@zC5 z@txgcw9njYV;3+Pf58-7LOK3{X}FB(xQZG09JA1kN-?2Ik}y~Lp_&~vPX=PX_)*JS zS|DTDh~;=lmSTx)M7?|$4YD1}75EZKA--#3^`m3?UQ z7sYWpXbE)TcY>g8ABKBvT{t5+j&nh)*J>VKYWG@|;iV3*#ceNfJNz%9$mNLREoJE( z-Sna;*xCK2mg_Bwb^qW1JuArJvxG?ik22LwOmZ{wn7AKXa6i7o8{Ldbd=GVa49oF2 z+Gw{PPZ+gd-h&RqSyugL1O7=LV$AeQFc~xb63qNvYZO!ATL>S=H3)FJup@-A||okU|;B@2vM z&5&cXi^6y(#lf@{M(q+OOJQN$L8PFSzuPfGRAcru%7jE`)_X99bk`f~dHT|u&PnO; z8BLeO_;yKQG&Y@13cc$L$J(no>tZh6km%IJyM&|nNzFk??JwzZgp~=T+pr#PlC5LB zN`jkPt7tRmh-84}2?sLc*vEwc_49~$jWxc;W#AT*=w~hpIeJ+W zQ=TVj-Xclf#%TPBEp-7iagl9xo?rQQ`ZQ@xZ~N59Aju-^Br0(>^D@yx0WNDwu0J-J zbxUHXrouRG1eL- z*JNmf-X={lj8!txV?ED~WNIp{b@|birZ}NArN~Ms&3H`pB&4Q-(w@(Q&R(hMIeMH& zb*agOsT%`eqnJ>=%{@xfqcTBF9a1qA*Wn>eGdbpwkUCJEW59h0pdP}xtgRoiAp
    -;z qKs8=wJeM2KCRxFqfjqsHJ1hCuB4KHTSIVVA9-ictHts9*we^48DpfoH