diff --git a/Main.java b/Main.java index 398bfe4..d609312 100644 --- a/Main.java +++ b/Main.java @@ -60,8 +60,8 @@ public class Main { MUTATIONRATE = 0.34; } else { // Ultra-hard sudoku - POPULATIONSIZE = 250000; - TOURNAMENTSIZE = 4; + POPULATIONSIZE = 500000; + TOURNAMENTSIZE = 3; MUTATIONRATE = 0.15; } @@ -311,7 +311,7 @@ public class Main { // Evaluate the fitness of the Sudoku by counting the number of row, column, and subgrid violations public void evaluateFitness() { - fitness = countRowViolations() + countColumnViolations() + countSubgridViolations(); + fitness = countRowViolations() + countColumnViolations(); } private int countRowViolations() { diff --git a/Testing/Main.java b/Testing/Main.java new file mode 100644 index 0000000..06e48f9 --- /dev/null +++ b/Testing/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/Testing/check.py b/Testing/check.py new file mode 100644 index 0000000..f38b3fb --- /dev/null +++ b/Testing/check.py @@ -0,0 +1,115 @@ +import subprocess +from time import sleep + +process = subprocess.Popen(['java', './Main.java'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + +info = [] +for i in range(9): + info.append([""] * 9) + +def check(x, y): + a = 0 + res = [] + if info[x][y] != "": + a += 1 + res.append(info[x][y]) + for dx in range(-1, 2): + for dy in range(-1, 2): + if (0 <= x+dx <= 8 and 0 <= y+dy <= 8 and (dy != 0 or dx != 0) and info[x+dx][y+dy] == 'A'): + a += 1 + res.append("P") + return [a, res] + if (0 <= x-1 <= 8 and info[x-1][y] == 'S' or 0 <= x+1 <= 8 and info[x+1][y] == 'S' or 0 <= y-1 <= 8 and info[x][y-1] == 'S' or 0 <= y+1 <= 8 and info[x][y+1] == 'S'): + a += 1 + res.append("P") + return [a, res] + + return [a, res] + + +def show(neo_x, neo_y): + with open('close.txt', encoding='utf-8', mode='w') as f1: + a = list(map) + a[map.find('𖨆')] = ' ' + a[map.find(str(neo_x) + " |") + 4 + neo_y * 4] = '𖨆' + f1.write(''.join(a)) + +map = '' +keymaker_x = 0 +keymaker_y = 0 +key_x = 99 +key_y = 99 +with open('field.txt', encoding='utf-8') as initial: + i = 1 + for line in initial: + map += line + if (3 <= i and i <= 19 and i % 2 == 1): + line = line.split('|')[1:-1] + for j in range(len(line)): + if (line[j] == ' ■ '): + info[i // 2 - 1][j] = 'A' + elif (line[j] == ' □ '): + info[i // 2 - 1][j] = 'S' + elif (line[j] == ' ⚷ '): + info[i // 2 - 1][j] = 'B' + key_x = i // 2 - 1 + key_y = j + elif (line[j] == ' K '): + info[i // 2 - 1][j] = 'K' + keymaker_x = i // 2 - 1 + keymaker_y = j + i += 1 + +# show(0, 0) +mode = 1 +process.stdin.write((str(mode) + "\n" + + str(keymaker_x) + " " + + str(keymaker_y) + "\n")) +process.stdin.flush() +print(str(mode) + "\n" + str(keymaker_x) + " " + str(keymaker_y)) + +now_x = 0 +now_y = 0 + +while True: + move = process.stdout.readline().replace('\n', '') + print(move) + if 'e' in move: + break + + move = move.split() + temp_x = int(move[1]) + temp_y = int(move[2]) + # show(temp_x, temp_y) + if not(temp_x + 1 == now_x and temp_y == now_y or temp_x - 1 == now_x and temp_y == now_y or temp_x == now_x and temp_y + 1 == now_y or temp_x == now_x and temp_y - 1 == now_y or temp_x == now_x and temp_y == now_y): + print("Error: Teleport") + break + now_x = int(move[1]) + now_y = int(move[2]) + # sleep(0.5) + + res = '' + num = 0 + + if mode == 1: + left = -1 + right = 2 + else: + left = -2 + right = 3 + + for dx in range(left, right): + for dy in range(left, right): + new_x = now_x + dx + new_y = now_y + dy + if 0 <= new_x <= 8 and 0 <= new_y <= 8: + a = check(new_x, new_y) + num += a[0] + for elem in a[1]: + res += str(new_x) + " " + str(new_y) + " " + elem + "\n" + + # Кодируем результат перед записью + send = str(num) + '\n' + res + process.stdin.write(send) + print(send[:-1]) + process.stdin.flush() \ No newline at end of file diff --git a/Testing/generate_stats.py b/Testing/generate_stats.py new file mode 100644 index 0000000..9d7342e --- /dev/null +++ b/Testing/generate_stats.py @@ -0,0 +1,180 @@ +import pandas as pd +from statistics import multimode, mean, median, stdev +import matplotlib.pyplot as plt +from matplotlib.backends.backend_pdf import PdfPages + +def calculate_statistics(execution_times): + """Calculate mean, median, mode, and standard deviation.""" + mean_time = mean(execution_times) + median_time = median(execution_times) + modes = multimode(execution_times) + std_dev = stdev(execution_times) + + # Handle multiple modes + if len(modes) == 1: + mode_time = modes[0] + else: + mode_time = modes # List of modes + + return mean_time, median_time, mode_time, std_dev + +def plot_histogram(execution_times, mean_time, median_time, mode_time, std_dev): + """Plot a histogram of execution times with mean, median, mode, and standard deviation.""" + plt.figure(figsize=(10, 6)) + + # Define bin range to focus between 25 and 35 ms + bin_start = 25 + bin_end = 35 + bins = list(range(bin_start, bin_end + 1)) # Bins from 25 to 35 + + # Plot the main histogram + plt.hist(execution_times, bins=bins, edgecolor='black', alpha=0.7, label='Execution Times (25-35 ms)') + + # Plot outliers (below 25 or above 35 ms) + outliers = [x for x in execution_times if x < bin_start or x > bin_end] + if outliers: + # Determine appropriate bins for outliers + outlier_min = min(outliers) + outlier_max = max(outliers) + outlier_bins = list(range(outlier_min, outlier_max + 2)) + plt.hist(outliers, bins=outlier_bins, edgecolor='black', alpha=0.7, color='red', label='Outliers (<25 or >35 ms)') + + plt.title('Histogram of Execution Times') + plt.xlabel('Execution Time (ms)') + plt.ylabel('Frequency') + + # Plot mean + plt.axvline(mean_time, color='blue', linestyle='dashed', linewidth=1.5, label=f'Mean: {mean_time:.2f} ms') + + # Plot median + plt.axvline(median_time, color='green', linestyle='dashed', linewidth=1.5, label=f'Median: {median_time} ms') + + # Plot mode(s) + if isinstance(mode_time, list): + for m in mode_time: + plt.axvline(m, color='purple', linestyle='dashed', linewidth=1.5, label=f'Mode: {m} ms') + else: + plt.axvline(mode_time, color='purple', linestyle='dashed', linewidth=1.5, label=f'Mode: {mode_time} ms') + + # Shade the area within one standard deviation from the mean + plt.axvspan(mean_time - std_dev, mean_time + std_dev, color='yellow', alpha=0.2, label='±1 Standard Deviation') + + # Set x-axis limits to focus on 25-35 ms with some padding for outliers + plt.xlim(bin_start - 5, bin_end + 5) # Extending a bit to show outliers + + plt.legend() + plt.tight_layout() + return plt.gcf() # Return the current figure + +def plot_boxplot(execution_times): + """Plot a box plot of execution times.""" + plt.figure(figsize=(10, 6)) + plt.boxplot(execution_times, vert=False, patch_artist=True, boxprops=dict(facecolor='lightblue')) + plt.title('Box Plot of Execution Times') + plt.xlabel('Execution Time (ms)') + plt.tight_layout() + return plt.gcf() + +def generate_pdf_report(csv_file, output_pdf): + """Generate a PDF report containing statistics and visualizations.""" + # Read the CSV file with error handling + try: + data = pd.read_csv(csv_file) + except FileNotFoundError: + print(f"Error: The file '{csv_file}' was not found.") + return + except pd.errors.EmptyDataError: + print(f"Error: The file '{csv_file}' is empty.") + return + except pd.errors.ParserError: + print(f"Error: The file '{csv_file}' does not appear to be in CSV format.") + return + + # Check if 'ExecutionTime_ms' column exists + if 'ExecutionTime_ms' not in data.columns: + print("Error: 'ExecutionTime_ms' column not found in the CSV file.") + return + + # Extract execution times + execution_times = data['ExecutionTime_ms'].tolist() + + # Validate execution times + if not execution_times: + print("Error: No execution time data found.") + return + + # Check for non-numeric values + non_numeric = [x for x in execution_times if not isinstance(x, (int, float))] + if non_numeric: + print("Error: Non-numeric values found in 'ExecutionTime_ms' column.") + print(non_numeric) + return + + # Check if there are enough data points for standard deviation + if len(execution_times) < 2: + print("Error: At least two execution time data points are required to calculate standard deviation.") + return + + # Calculate statistics + mean_time, median_time, mode_time, std_dev = calculate_statistics(execution_times) + + # Debug print statements + print(f"Mean: {mean_time:.2f} ms") + print(f"Median: {median_time} ms") + print(f"Mode: {mode_time if isinstance(mode_time, list) else [mode_time]} ms") + print(f"Standard Deviation: {std_dev:.2f} ms") + + # Create histogram plot with standard deviation shaded + fig_hist = plot_histogram(execution_times, mean_time, median_time, mode_time, std_dev) + + # Create box plot + fig_box = plot_boxplot(execution_times) + + # Prepare statistics text + if isinstance(mode_time, list): + mode_str = ', '.join(map(str, mode_time)) + else: + mode_str = str(mode_time) + + stats_text = f""" + Execution Time Statistics + ========================= + + Total Runs: {len(execution_times)} + + Mean: {mean_time:.2f} ms + Median: {median_time} ms + Mode: {mode_str} ms + Standard Deviation: {std_dev:.2f} ms + """ + + # Create PDF + with PdfPages(output_pdf) as pdf: + # Page 1: Histogram + pdf.savefig(fig_hist) + plt.close(fig_hist) + + # Page 2: Box Plot + pdf.savefig(fig_box) + plt.close(fig_box) + + # Page 3: Statistics Summary + plt.figure(figsize=(8.5, 11)) + plt.axis('off') # Hide axes + + # Add text to the figure + plt.text(0.5, 0.5, stats_text, horizontalalignment='center', verticalalignment='center', fontsize=12, wrap=True) + + # Add the statistics page to the PDF + pdf.savefig() + plt.close() + + print(f"PDF report '{output_pdf}' has been generated successfully.") + +if __name__ == "__main__": + # Define input and output files + csv_file = 'execution_times.csv' + output_pdf = 'execution_time_report.pdf' + + # Generate the PDF report + generate_pdf_report(csv_file, output_pdf) diff --git a/Testing/report (2).py b/Testing/report (2).py new file mode 100644 index 0000000..e725f91 --- /dev/null +++ b/Testing/report (2).py @@ -0,0 +1,238 @@ +import subprocess +from random import randint +import timeit +import statistics + +# astar = ['python', './astar.py'] +# backtracking = ['python', './bt.exe'] +astar = ['./code.exe'] +backtracking = ['./bt.exe'] + +process1 = subprocess.Popen(astar, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +process2 = subprocess.Popen(astar, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +process3 = subprocess.Popen(backtracking, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + +wins_Astar = 0 +losses_Astar = 0 +wins_bt = 0 +losses_bt = 0 + +def check(x, y): + a = 0 + res = [] + if info[x][y] != "": + a += 1 + res.append(info[x][y]) + for dx in range(-1, 2): + for dy in range(-1, 2): + if (0 <= x+dx <= 8 and 0 <= y+dy <= 8 and info[x+dx][y+dy] == 'A'): + a += 1 + res.append("P") + return [a, res] + if (0 <= x-1 <= 8 and info[x-1][y] == 'S' or 0 <= x+1 <= 8 and info[x+1][y] == 'S' or 0 <= y-1 <= 8 and info[x][y-1] == 'S' or 0 <= y+1 <= 8 and info[x][y+1] == 'S'): + a += 1 + res.append("P") + return [a, res] + + return [a, res] + +def Astar_mode_1(): + global info, process1, wins_Astar, losses_Astar, keymaker_x, keymaker_y, key_x, key_y + mode = 1 + process1.stdin.write(str(mode) + "\n" + str(keymaker_x) + " " + str(keymaker_y) + "\n") + process1.stdin.flush() + now_x = 0 + now_y = 0 + + while True: + move = process1.stdout.readline().strip() + if 'e' in move: + if (move[-2:] == '-1'): + losses_Astar += 1 + else: + wins_Astar += 1 + break + move = move.split() + now_x = int(move[1]) + now_y = int(move[2]) + + res = '' + num = 0 + + if mode == 1: + left = -1 + right = 2 + else: + left = -2 + right = 3 + + for dx in range(left, right): + for dy in range(left, right): + new_x = now_x + dx + new_y = now_y + dy + if 0 <= new_x <= 8 and 0 <= new_y <= 8: + a = check(new_x, new_y) + num += a[0] + for elem in a[1]: + res += str(new_x) + " " + str(new_y) + " " + elem + "\n" + + process1.stdin.write(str(num) + '\n' + res) + process1.stdin.flush() + +def Astar_mode_2(): + global info, process2, keymaker_x, keymaker_y, key_x, key_y + mode = 2 + process2.stdin.write(str(mode) + "\n" + str(keymaker_x) + " " + str(keymaker_y) + "\n") + process2.stdin.flush() + now_x = 0 + now_y = 0 + + while True: + move = process2.stdout.readline().strip() + if 'e' in move: + break + move = move.split() + now_x = int(move[1]) + now_y = int(move[2]) + + res = '' + num = 0 + + if mode == 1: + left = -1 + right = 2 + else: + left = -2 + right = 3 + + for dx in range(left, right): + for dy in range(left, right): + new_x = now_x + dx + new_y = now_y + dy + if 0 <= new_x <= 8 and 0 <= new_y <= 8: + a = check(new_x, new_y) + num += a[0] + for elem in a[1]: + res += str(new_x) + " " + str(new_y) + " " + elem + "\n" + + process2.stdin.write(str(num) + '\n' + res) + process2.stdin.flush() + +def back(): + global info, process3, wins_bt, losses_bt, keymaker_x, keymaker_y, key_x, key_y + mode = randint(1, 2) + process3.stdin.write(str(mode) + "\n" + str(keymaker_x) + " " + str(keymaker_y) + "\n") + process3.stdin.flush() + now_x = 0 + now_y = 0 + + while True: + move = process3.stdout.readline().strip() + if 'e' in move: + if (move[-2:] == '-1'): + losses_bt += 1 + else: + wins_bt += 1 + break + move = move.split() + now_x = int(move[1]) + now_y = int(move[2]) + + res = '' + num = 0 + + if mode == 1: + left = -1 + right = 2 + else: + left = -2 + right = 3 + + for dx in range(left, right): + for dy in range(left, right): + new_x = now_x + dx + new_y = now_y + dy + if 0 <= new_x <= 8 and 0 <= new_y <= 8: + a = check(new_x, new_y) + num += a[0] + for elem in a[1]: + res += str(new_x) + " " + str(new_y) + " " + elem + "\n" + + process3.stdin.write(str(num) + '\n' + res) + process3.stdin.flush() + +def mapgen(): + global info, keymaker_x, keymaker_y, key_x, key_y, process1, process2, process3, astar, backtracking + + info = [] + for i in range(9): + info.append([""] * 9) + info[0][0] = 'N' + keymaker_x = randint(0, 8) + keymaker_y = randint(0, 8) + while (info[keymaker_x][keymaker_y] != ''): + keymaker_x = randint(0, 8) + keymaker_y = randint(0, 8) + info[keymaker_x][keymaker_y] = 'K' + + key_x = randint(0, 8) + key_y = randint(0, 8) + while (info[key_x][key_y] != ""): + key_x = randint(0, 8) + key_y = randint(0, 8) + info[key_x][key_y] = 'B' + + for smith in range(randint(0, 3)): + x = randint(0, 8) + y = randint(0, 8) + while (x == 0 and y == 0 or x == 0 and y == 1 or x == 1 and y == 0 or x == 1 and y == 1 or info[x][y] != '' or (x-1 >= 0 and y-1 >=0 and (info[x-1][y-1] == 'K' or info[x-1][y-1] == "B")) or (x-1 >= 0 and (info[x-1][y] == 'K' or info[x-1][y] == 'B')) or (x-1 >= 0 and y+1 <= 8 and (info[x-1][y+1] == 'K' or info[x-1][y+1] == 'B')) or (y-1 >= 0 and (info[x][y-1] == 'K' or info[x][y-1] == 'B')) or (y+1 <= 8 and (info[x][y+1] == 'K' or info[x][y+1] == 'B')) or (x+1 <= 8 and y-1 >= 0 and (info[x+1][y-1] == 'K' or info[x+1][y-1] == 'B')) or (x+1 <= 8 and (info[x+1][y] == 'K' or info[x+1][y] == 'B')) or (x+1 <= 8 and y+1 <= 8 and (info[x+1][y+1] == 'K' or info[x+1][y+1] == 'B'))): + x = randint(0, 8) + y = randint(0, 8) + info[x][y] = 'A' + + for sentiel in range(randint(0, 1)): + x = randint(0, 8) + y = randint(0, 8) + while (x == 0 and y == 0 or x == 0 and y == 1 or x == 1 and y == 0 or info[x][y] != '' or (x-1 >= 0 and (info[x-1][y] == 'K' or info[x-1][y] == 'B')) or (x+1 <= 8 and (info[x+1][y] == 'K' or info[x+1][y] == 'B')) or (y-1 >= 0 and (info[x][y-1] == 'K' or info[x][y-1] == 'B')) or (y+1 <= 8 and (info[x][y+1] == 'K' or info[x][y+1] == 'B'))): + x = randint(0, 8) + y = randint(0, 8) + info[x][y] = 'S' + + info[0][0] = '' + process1 = subprocess.Popen(astar, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + process2 = subprocess.Popen(astar, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + process3 = subprocess.Popen(backtracking, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + +execution_time_1 = [] +execution_time_2 = [] +execution_time_3 = [] +for i in range(1, 1001): + mapgen() + execution_time_1.append(timeit.timeit(Astar_mode_1, number=1) * 1000000) + execution_time_2.append(timeit.timeit(Astar_mode_2, number=1) * 1000000) + execution_time_3.append(timeit.timeit(back, number=1) * 1000000) + if (i % 100 == 0): + print('Запущено карт', i) + +print("Execution time (A* mode 1)") +print("Mean:", statistics.mean(execution_time_1)) +print("Mode:", statistics.mode(execution_time_1)) +print("Median:", statistics.median(execution_time_1)) +print("Standart deviation:", statistics.stdev(execution_time_1)) +print() +print("Execution time (A* mode 2)") +print("Mean:", statistics.mean(execution_time_2)) +print("Mode:", statistics.mode(execution_time_2)) +print("Median:", statistics.median(execution_time_2)) +print("Standart deviation:", statistics.stdev(execution_time_2)) +print() +print("Execution time (Backtrack)") +print("Mean:", statistics.mean(execution_time_3)) +print("Mode:", statistics.mode(execution_time_3)) +print("Median:", statistics.median(execution_time_3)) +print("Standart deviation:", statistics.stdev(execution_time_3)) +print() +print("Wins A*:", wins_Astar) +print("Losses A*:", losses_Astar) +print("Wins bt:", wins_bt) +print("Losses bt:", losses_bt) diff --git a/Testing/run_python_1000_times (2).sh b/Testing/run_python_1000_times (2).sh new file mode 100644 index 0000000..9f72a8c --- /dev/null +++ b/Testing/run_python_1000_times (2).sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Initialize the CSV file and write the header +echo "Run,ExecutionTime_ms,Result" > execution_times.csv + +FILE_NAME=Main.java + +for i in {1..1000} +do + # Record the start time in milliseconds + start_time=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time()*1000)') + + # Execute the Python script and capture its output + # Use `stdout` and `stderr` to capture all outputs + output=$(python "$FILE_NAME" 2>&1) + + # Record the end time in milliseconds + end_time=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time()*1000)') + + # Calculate the elapsed time + elapsed_time=$((end_time - start_time)) + + # Extract the result from the Python script's output + # Assumes the output is in the format "e " + result_line=$(echo "$output" | grep '^e ') + + if [ -n "$result_line" ]; then + # Extract the number after 'e ' + number=$(echo "$result_line" | awk '{print $2}') + + # Determine Result as 1 or 0 based on the number + if [ "$number" -gt 0 ]; then + result=1 + elif [ "$number" -eq -1 ]; then + result=0 + else + # Handle unexpected numbers + result="Unexpected_$number" + echo "Run $i: $elapsed_time ms, Result: $result (Unexpected number)" + fi + else + # If the expected line is not found + result="N/A" + echo "Run $i: $elapsed_time ms, Result: $result (Missing 'e ' in output)" + fi + + # Log the execution time and result + echo "Run $i: $elapsed_time ms, Result: $result" + + # Append the run number, elapsed time, and result to the CSV file + echo "$i,$elapsed_time,$result" >> execution_times.csv +done diff --git a/Testing/run_python_1000_times.sh b/Testing/run_python_1000_times.sh new file mode 100644 index 0000000..25d879f --- /dev/null +++ b/Testing/run_python_1000_times.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Initialize the CSV file and write the header +echo "Run,ExecutionTime_ms" > execution_times.csv + +FILE_NAME=save_neo.py + +for i in {1..1000} +do + start_time=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time()*1000)') + python $FILE_NAME + end_time=$(perl -MTime::HiRes=time -e 'printf("%.0f\n", time()*1000)') + elapsed_time=$(($end_time - $start_time)) + echo "Run $i: $elapsed_time ms" + # Append the result to the CSV file + echo "$i,$elapsed_time" >> execution_times.csv +done diff --git a/Testing/stat.py b/Testing/stat.py new file mode 100644 index 0000000..3fd4eb9 --- /dev/null +++ b/Testing/stat.py @@ -0,0 +1,186 @@ +import subprocess +from time import time +import matplotlib.pyplot as plt +import random +from dokusan import generators + +# C++ коды +#code = ["./build/sudoku"] +# Java коды +code = ["java", "./Main.java"] +# Python коды +# code = ["python", "./submit.py"] + +N_TESTS = 30 + +def read_sudoku(file): + sudoku = [] + i = 0 + for line in file: + i += 1 + if (i == 10): + break + row = list(map(int, line.split())) + sudoku.append(row) + return sudoku + +def is_valid_sudoku(sudoku, input_file): + # Проверка строк + for row in sudoku: + if len(set(row)) != 9 or any(num < 1 or num > 9 for num in row): + print('строка', row) + return False + + # Проверка столбцов + for col in range(9): + column = [sudoku[row][col] for row in range(9)] + if len(set(column)) != 9: + print('столбец', col) + return False + + # Проверка 3x3 квадратов + for box_row in range(0, 9, 3): + for box_col in range(0, 9, 3): + square = [] + for i in range(3): + for j in range(3): + square.append(sudoku[box_row + i][box_col + j]) + if len(set(square)) != 9: + print('квадрат') + return False + + # Проверка совпадения с input + row = 0 + for line in input_file: + a = line.split() + for column in range(9): + if a[column] != '-' and int(a[column]) != sudoku[row][column]: + print('строка', row) + return False + row += 1 + + return True + +def mapgen(numbers, input_file): + # Сгенерировать полный решённый Судоку + full_sudoku = list(map(int, str(generators.random_sudoku(avg_rank=0)))) + grid = [full_sudoku[i:i+9] for i in range(0, 81, 9)] + + # Составить список всех координат + coords = [(i, j) for i in range(9) for j in range(9)] + random.shuffle(coords) + + # Удаление чисел с проверкой на уникальность решения + while sum(row.count(0) for row in grid) < (81 - numbers) and coords: + x, y = coords.pop() + grid[x][y] = 0 + + # Записать результат в файл + for row in grid: + input_file.write(" ".join(map(str, row)).replace('0', '-') + "\n") + +def main(): + exec_time_avg_easy = [] + avg_fitness_avg_easy = [] + max_fitness_avg_easy = [] + exec_time_avg_medium = [] + avg_fitness_avg_medium = [] + max_fitness_avg_medium = [] + exec_time_avg_hard = [] + avg_fitness_avg_hard = [] + max_fitness_avg_hard = [] + + exec_time_avg = [] + avg_fitness_avg = [] + max_fitness_avg = [] + number_of_cells = [] + a = 21 + b = 41 + for cells in range(a, b): + exec_time = [] + avg_fitness = [] + max_fitness = [] + for maps in range(N_TESTS): + number_of_cells.append(cells) + + # генерация карты + with open("input.txt", "w") as input_file: + mapgen(cells, input_file) + + # запуск алгоритма + with open("input.txt", "r") as input_file, open("output.txt", "w") as output_file: + start = time() + process1 = subprocess.Popen(code, stdin=input_file, stdout=output_file, stderr=subprocess.PIPE, text=True) + process1.wait() + exec_time.append(round(time() - start, 2)) + print('Тест', cells, maps, 'пройден за', exec_time[-1]) + + # проверка на корректность решения + with open("input.txt", "r") as input_file, open("output.txt", "r") as output_file: + read = output_file.readlines() + avg_fitness.append(float(read[1])) + max_fitness.append(float(read[0])) + read.pop(1) + read.pop(0) + sudoku = read_sudoku(read) + if not is_valid_sudoku(sudoku, input_file): + print("Решение судоку некорректное.") + exit() + if (30 <= cells <= 40): + exec_time_avg_easy += exec_time + avg_fitness_avg_easy += avg_fitness + max_fitness_avg_easy += max_fitness + elif (26 <= cells <= 29): + exec_time_avg_medium += exec_time + avg_fitness_avg_medium += avg_fitness + max_fitness_avg_medium += max_fitness + else: + exec_time_avg_hard += exec_time + avg_fitness_avg_hard += avg_fitness + max_fitness_avg_hard += max_fitness + exec_time_avg.append(sum(exec_time) / len(exec_time)) + avg_fitness_avg.append(sum(avg_fitness) / len(avg_fitness)) + max_fitness_avg.append(sum(max_fitness) / len(max_fitness)) + + print('EASY') + print('average time', sum(exec_time_avg_easy) / len(exec_time_avg_easy)) + print('maximum fitness', sum(max_fitness_avg_easy) / len(max_fitness_avg_easy)) + print('average fitness', sum(avg_fitness_avg_easy) / len(avg_fitness_avg_easy)) + print() + print('MEDIUM') + print('average time', sum(exec_time_avg_medium) / len(exec_time_avg_medium)) + print('maximum fitness', sum(max_fitness_avg_medium) / len(max_fitness_avg_medium)) + print('average fitness', sum(avg_fitness_avg_medium) / len(avg_fitness_avg_medium)) + print() + print('HARD') + print('average time', sum(exec_time_avg_hard) / len(exec_time_avg_hard)) + print('maximum fitness', sum(max_fitness_avg_hard) / len(max_fitness_avg_hard)) + print('average fitness', sum(avg_fitness_avg_hard) / len(avg_fitness_avg_hard)) + plt.figure(1) + plt.plot([i for i in range(a, b)], avg_fitness_avg, linestyle='-', color='b') + plt.title(f'Average avg fitness on last generation among {N_TESTS} tests per each N') + plt.xlabel('Numbers provided (N)') + plt.ylabel('Average avg fitness on last generation') + plt.grid() + plt.savefig(f"avgfit{N_TESTS}.png", dpi=400) + + plt.figure(2) + plt.plot([i for i in range(a, b)], exec_time_avg, linestyle='-', color='b') + plt.title(f'Average execution time among {N_TESTS} tests per each N') + plt.xlabel('Numbers provided (N)') + plt.ylabel('Average execution time, sec') + plt.grid() + plt.savefig(f"exec{N_TESTS}.png", dpi=400) + + plt.figure(3) + plt.plot([i for i in range(a, b)], max_fitness_avg, linestyle='-', color='b') + plt.title(f'Average max fitness on last generation among {N_TESTS} tests per each N') + plt.xlabel('Numbers provided (N)') + plt.ylabel('Average max fitness on last generation') + plt.grid() + plt.savefig(f"maxfit{N_TESTS}.png", dpi=400) + + plt.show() + +if __name__ == "__main__": + main() diff --git a/Testing/util.py b/Testing/util.py new file mode 100644 index 0000000..4354f5e --- /dev/null +++ b/Testing/util.py @@ -0,0 +1,187 @@ +# POSSIBLE USAGE KEYS +# map, keymaker_position = Utils.generate_random_map() +# proceed with map actions... + + +import random +from typing import ( + List, + Tuple, + Optional, + Set, +) + + +class Utils: + @staticmethod + def generate_random_map() -> Tuple[List[List[str]], Optional[Tuple[int, int]]]: + """ + Generates a random 9x9 game map with placements of 'A', 'S', and 'P'. + 'P' placements depend on the positions of 'A' and 'S'. + + Returns: + A tuple containing the game map and one unoccupied square (or None if all occupied). + """ + # Initialize a 9x9 grid with empty strings + game_map: List[List[str]] = [[[] for _ in range(9)] for _ in range(9)] + all_coordinates: List[Tuple[int, int]] = [(x, y) for x in range(9) for y in range(9)] + + def place_letter( + letter: str, + count: int, + available: List[Tuple[int, int]], + ) -> List[Tuple[int, int]]: + """ + Places a specified letter on the game map a certain number of times. + + Args: + letter: The letter to place ('A' or 'S'). + count: Number of times to place the letter. + available: List of available coordinates. + + Returns: + A list of coordinates where the letter was placed. + """ + placed: List[Tuple[int, int]] = [] + for _ in range(count): + if not available: + break + x, y = random.choice(available) + game_map[x][y] = [letter] + placed.append((x, y)) + available.remove((x, y)) + + return placed + + # Place "A" 0 to 3 times + num_A: int = random.randint(0, 3) + A_positions: List[Tuple[int, int]] = place_letter("A", num_A, all_coordinates) + + # Place "S" 0 to 1 times + num_S: int = random.randint(0, 1) + S_positions: List[Tuple[int, int]] = place_letter("S", num_S, all_coordinates) + + def get_moore_neighbors(x: int, y: int) -> List[Tuple[int, int]]: + """ + Retrieves all Moore neighbors (8 surrounding cells) for a given position. + + Args: + x: X-coordinate. + y: Y-coordinate. + + Returns: + A list of neighboring coordinates within bounds. + """ + neighbors: List[Tuple[int, int]] = [] + for dx in [-1, 0, 1]: + for dy in [-1, 0, 1]: + if dx == 0 and dy == 0: + continue + nx, ny = x + dx, y + dy + if 0 <= nx < 9 and 0 <= ny < 9: + neighbors.append((nx, ny)) + + return neighbors + + def get_von_neumann_neighbors(x: int, y: int) -> List[Tuple[int, int]]: + """ + Retrieves all von Neumann neighbors (4 adjacent cells) for a given position. + + Args: + x: X-coordinate. + y: Y-coordinate. + + Returns: + A list of neighboring coordinates within bounds. + """ + neighbors: List[Tuple[int, int]] = [] + for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + nx, ny = x + dx, y + dy + if 0 <= nx < 9 and 0 <= ny < 9: + neighbors.append((nx, ny)) + + return neighbors + + # Collect all possible P placement positions + possible_P_positions: Set[Tuple[int, int]] = set() + + for x, y in A_positions: + neighbors = get_moore_neighbors(x, y) + possible_P_positions.update(neighbors) + + for x, y in S_positions: + neighbors = get_von_neumann_neighbors(x, y) + possible_P_positions.update(neighbors) + + # Remove positions already occupied by "A" or "S" + occupied_positions: Set[Tuple[int, int]] = set(A_positions + S_positions) + possible_P_positions = [ + pos + for pos in possible_P_positions + if pos not in occupied_positions and game_map[pos[0]][pos[1]] == [] + ] + + # Place "P" in all possible positions derived from "A" and "S" + for x, y in possible_P_positions: + game_map[x][y] = ["P"] + if (x, y) in all_coordinates: + all_coordinates.remove((x, y)) + + # Select one unoccupied square + chosen_unoccupied: Optional[Tuple[int, int]] = ( + random.choice(all_coordinates) if all_coordinates else None + ) + + return game_map, chosen_unoccupied + + @staticmethod + def heuristic(pos: Tuple[int, int], goal: Tuple[int, int]) -> int: + """ + Calculates the Manhattan distance between two positions. + + Args: + pos: Current position as (x, y). + goal: Goal position as (x, y). + + Returns: + The Manhattan distance as an integer. + """ + return abs(pos[0] - goal[0]) + abs(pos[1] - goal[1]) + + @staticmethod + def get_directions(pos: Tuple[int, int]) -> List[Tuple[int, int]]: + """ + Returns possible moves (Up, Down, Left, Right) from the current position within bounds. + + Args: + pos: Current position as (x, y). + + Returns: + A list of valid adjacent positions. + """ + moves: List[Tuple[int, int]] = [ + (pos[0] + 1, pos[1]), # Down + (pos[0] - 1, pos[1]), # Up + (pos[0], pos[1] + 1), # Right + (pos[0], pos[1] - 1), # Left + ] + + return [move for move in moves if 0 <= move[0] <= 8 and 0 <= move[1] <= 8] + + @staticmethod + def get_directions_with_zones( + pos: Tuple[int, int], enemies_perception_zones: Set[Tuple[int, int]] + ) -> List[Tuple[int, int]]: + """ + Returns possible moves from the current position excluding moves that are in danger zones. + + Args: + pos: Current position as (x, y). + enemies_perception_zones: A set of dangerous positions. + + Returns: + A list of safe adjacent positions. + """ + moves: List[Tuple[int, int]] = Utils.get_directions(pos) + + return [move for move in moves if move not in enemies_perception_zones] diff --git a/input.txt b/input.txt index 5ff6b13..d13c95d 100644 --- a/input.txt +++ b/input.txt @@ -1,9 +1,9 @@ -- 8 - - - - - 9 - -- - 7 5 - 2 8 - - -6 - - 8 - 7 - - 5 -3 7 - - 8 - - 5 1 -2 - - - - - - - 8 -9 5 - - 4 - - 3 2 -8 - - 1 - 4 - - 9 -- - 1 9 - 3 6 - - -- 4 - - - - - 2 - +- - - 8 5 6 - - - +- - - 1 9 - - - - +5 - - - - 7 - 1 - +- 2 - - - 9 - 7 5 +- 9 - - - 1 2 - 3 +- - - - 3 - 1 - - +- 3 - - - - - 2 - +- - - - - - - - - +- - 1 - - - - - - diff --git a/statistical/demo/src/main/java/com/example/Main.java b/statistical/demo/src/main/java/com/example/Main.java index c1d0c74..edf5245 100644 --- a/statistical/demo/src/main/java/com/example/Main.java +++ b/statistical/demo/src/main/java/com/example/Main.java @@ -66,8 +66,8 @@ public class Main { MUTATIONRATE = 0.34; } else { // Ultra-hard sudoku - POPULATIONSIZE = 500000; - TOURNAMENTSIZE = 3; + POPULATIONSIZE = 100000; + TOURNAMENTSIZE = 10; MUTATIONRATE = 0.1; } @@ -109,7 +109,7 @@ public class Main { generation++; // Plot the fitness graph every 10 generations - if (generation % 1 == 0) { + if (generation % 10 == 0) { mainInstance.plotFitness(fitnessValues); } diff --git a/statistical/demo/target/classes/com/example/Main.class b/statistical/demo/target/classes/com/example/Main.class index 9d0c4b8..f46315d 100644 Binary files a/statistical/demo/target/classes/com/example/Main.class and b/statistical/demo/target/classes/com/example/Main.class differ