├── LICENSE ├── README.md ├── sudoku.cpp └── sudoku.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stefan Dascalescu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SudokuSolver 2 | 3 | ### What is Sudoku? 4 | 5 | Sudoku is a logic-based combinatorial number-placement puzzle. In classic Sudoku, the objective is to fill a 9x9 grid with digits so that each line, column and each of the 9 3x3 subgrids that compose the grid contain all of the digits from 1 to 9. The puzzle starts with a partially completed grid and it has a unique solution. 6 | 7 | ### Why did I create a Sudoku Solver? 8 | 9 | I have always been interested in logic puzzles and mathematical games and as I was playing sudoku with my girlfriend, I wanted to learn more about the strategies which can be used to solve more and more grids while being faster and I realized that it could be a good opportunity to end up implementing a solver using my favorite programming languages. 10 | 11 | ### How does the solver work? 12 | 13 | At first, I am using an algorithm based on a few sudoku strategies which fill as many squares as possible, using only notes and logic. The algorithm works as following: 14 | 15 | * Add notes at the beginning (i. e: mark all the possible squares with all the possible digits according to the rules of sudoku) 16 | * While the sudoku is not completed, do the following: 17 | 1. Whenever one of these situations occurs, fill the square with the number which fits the description 18 | 1. If in a 3x3 grid there is only one position with a certain number, fill it 19 | 2. If in a 1x9 grid there is only one position with a certain number, fill it 20 | 3. If in a 9x1 grid there is only one position with a certain number, fill it 21 | 2. Notes reduction 22 | 1. After completing each square, remove all the notes with that value on the 3x3, 1x9 and 9x1 grids 23 | 2. If in a 3x3 grid there is only one row/column with a certain number, remove it from the other 3x3 from the same row/column 24 | 3. If in a 3x3, 1x9, 9x1 grid there is a case where the size of a set is equal to the number of empty slots, remove the numbers from the other slots from 3x3 and 1x9/9x1 grids, depending on the case 25 | 4. If in a 3x3, 1x9, 9x1 grid there is a case where the size of a set is equal to the number of empty slots the numbers show up, remove the numbers from the other slots from 3x3 and 1x9/9x1 grids, depending on the case 26 | 27 | 28 | * The algorithm will keep running as long as there has been some modification done during the previous step (either a new square gets solved or some notes have been removed) 29 | 30 | Then, in case the grid has not been filled completely, I am going to use the remaining notes in order to run a simple brute force generator until it finds the solution of the sudoku algorithm. Because a large part of the sudoku has been filled already using the logic and the notes, the brute force generator will run really fast and the solution will be found in a matter of milliseconds. 31 | 32 | ### Implementing the solver in C++ and Python 33 | 34 | The algorithm has been firstly implemented in C++, which is by far faster than Python and this allowed me to test the performance of the program in a more proper manner. After implementing it in C++, I proceeded to translate it in Python, and while for the grids which can be solved using only logic and notes it is doing rather well, it is struggling with the ones which require brute force, because Python is well known for its bad performance when it comes to recursive algorithms. 35 | 36 | ### Data regarding the performance of the Sudoku solver in the context of the three datasets I used 37 | 38 | In order to test my program’s performance, I used three datasets, using 1 million grids from each of them. 39 | 40 | 1. https://www.kaggle.com/radcliffe/3-million-sudoku-puzzles-with-ratings 41 | 2. https://www.kaggle.com/bryanpark/sudoku 42 | 3. https://www.kaggle.com/rohanrao/sudoku 43 | 44 | The input for each of the datasets is available here: https://drive.google.com/drive/folders/13w9UBtqPJ4czkSe5N5eQL7L9LZYRf6-l?usp=sharing 45 | 46 | The results for each of the datasets, assuming the grids were added in a random order 47 | 48 | | No. of grids | set 1 | set 2 | set 3 | 49 | | :---: | :---: | :---: | :---: | 50 | |100 | 0.585s| 0.176s| 0.266s | 51 | |500 | 2.997s| 0.876s| 1.372s| 52 | | 1000| 6.053s| 1.736s| 2.75s| 53 | |2000 | 11.916s| 3.457s| 5.495s| 54 | |4000 |23.587s | 6.916s| 10.899s| 55 | |5000 | 29.508s| 8.641s| 13.644s| 56 | | 8000| 46.93s| 13.8s| 21.915s| 57 | |10000 | 58.516s| 17.249s| 27.351s| 58 | |20000 |117.202s |34.558s |54.229s | 59 | | 40000| 233.799s| 69.073s| 109.298s| 60 | |50000 |292.041s | 86.489s| 136.66s| 61 | | 100000|583.539s |172.958s |272.869s | 62 | |200000 |1166.14s |345.964s |544.796s | 63 | | 400000| 2330.89s|698.793s |1090.5s | 64 | |500000 |2913.3s |871.366s |1364.44s | 65 | |800000 |4659.17s |1394.55s |2187.0s | 66 | |1000000 |5823.8s |1745.38s | 2736.72s| 67 | 68 | Since the first dataset has more details regarding the difficulty levels of each grid, I decided to also test the performance of my algorithm against the most difficult 100000 grids. I have sorted the data in decreasing order of difficulty so that on each row, we will see the difficulty level 69 | 70 | |No. of grids |Set 1 | 71 | | :---: | :---: | 72 | |100 | 1.022s| 73 | | 500| 5.331s| 74 | |1000 | 10.133s| 75 | |2000 |19.408s | 76 | |4000 |36.588s | 77 | |5000 | 45.443s| 78 | |8000 | 70.233s| 79 | |10000 |86.341s | 80 | |20000 | 163.586s| 81 | | 40000|309.59s | 82 | | 50000| 381.352s| 83 | |100000 |730.243s | 84 | 85 | As the number of grids increases, the ratio between the time required for a random order of grids and the set of hardest grids decreases, because more and more easier grids will also be featured in the dataset. It is worth noting that for the hardest 100 grids, the program needs around 1.022s, which suggests a speed of approximately 100 grids per second for the hardest grids. 86 | 87 | ### Conclusions 88 | 89 | 1. On average, my sudoku solver algorithm is able to do ~291.03 grids per second, which means around ~0.00343 seconds for each grid on average 90 | 2. The first set is the one which contains the hardest grids, followed by the third one and followed by the second one 91 | 3. It is likely that the speed of the algorithm can be improved by optimizing the brute force part, by starting with a certain square with only two hints or by dealing with notes removal while implementing the brute force part as well. 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /sudoku.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | 12 | ifstream f("input.in"); // using file IO for processing the input, it's way easier when it comes to big inputs 13 | ofstream g("output.out"); 14 | 15 | int mat[9][9]; // the sudoku grid 16 | 17 | int correct[9][9]; // the correct solution, for the sake of testing from the dataset 18 | 19 | int notes[9][9][10]; // list of notes 20 | 21 | int line_borders_3x3[9][9]; 22 | int column_borders_3x3[9][9]; 23 | 24 | bool frq[1000002]; 25 | 26 | void add_notes() 27 | { 28 | for(int line = 0; line <= 8; ++line) 29 | for(int column = 0; column <= 8; ++column) 30 | if(mat[line][column] == 0) 31 | { 32 | for(int digit = 1; digit <= 9; ++digit) 33 | { 34 | bool added = 1; 35 | for(int pos_line = 0; pos_line <= 8; ++pos_line) 36 | if(mat[pos_line][column] == digit) 37 | added = 0; 38 | for(int pos_column = 0; pos_column <= 8; ++pos_column) 39 | if(mat[line][pos_column] == digit) 40 | added = 0; 41 | for(int pos_line = line_borders_3x3[line][column]; pos_line <= line_borders_3x3[line][column] + 2; ++pos_line) 42 | for(int pos_column = column_borders_3x3[line][column]; pos_column <= column_borders_3x3[line][column] + 2; ++pos_column) 43 | if(mat[pos_line][pos_column] == digit) 44 | added = 0; 45 | notes[line][column][digit] = added; 46 | } 47 | } 48 | } 49 | 50 | int stuck = 0, solved_count = 0; 51 | 52 | void remove_notes_rows(int line, int digit) 53 | { 54 | for(int column = 0; column <= 8; ++column) 55 | notes[line][column][digit] = 0; 56 | } 57 | 58 | void remove_notes_columns(int column, int digit) 59 | { 60 | for(int line = 0; line <= 8; ++line) 61 | notes[line][column][digit] = 0; 62 | } 63 | 64 | void remove_notes_3x3(int line, int column, int digit) 65 | { 66 | for(int poz_line = line; poz_line <= line + 2; ++poz_line) 67 | for(int poz_column = column; poz_column <= column + 2; ++poz_column) 68 | notes[poz_line][poz_column][digit] = 0; 69 | } 70 | 71 | void remove_notes_spot(int line, int column) 72 | { 73 | for(int digit = 1; digit <= 9; ++digit) 74 | notes[line][column][digit] = 0; 75 | } 76 | 77 | void activate_removals(int line, int column, int digit) 78 | { 79 | ++solved_count; 80 | stuck = 0; 81 | mat[line][column] = digit; 82 | remove_notes_spot(line, column); 83 | remove_notes_rows(line, digit); 84 | remove_notes_columns(column, digit); 85 | remove_notes_3x3(line_borders_3x3[line][column], column_borders_3x3[line][column], digit); 86 | } 87 | 88 | void unique_row() 89 | { 90 | for(int line = 0; line <= 8; ++line) 91 | for(int digit = 1; digit <= 9; ++digit) 92 | { 93 | bool exists = 0; 94 | for(int column = 0; column <= 8; ++column) 95 | if(mat[line][column] == digit) 96 | exists = 1; 97 | if(exists == 0) 98 | { 99 | int note_column = -1; 100 | int count = 0; 101 | for(int column = 0; column <= 8; ++column) 102 | if(notes[line][column][digit] == 1) 103 | { 104 | ++count; 105 | note_column = column; 106 | } 107 | if(count == 1) 108 | activate_removals(line, note_column, digit); 109 | } 110 | } 111 | } 112 | 113 | void unique_column() 114 | { 115 | for(int column = 0; column <= 8; ++column) 116 | for(int digit = 1; digit <= 9; ++digit) 117 | { 118 | bool exists = 0; 119 | for(int line = 0; line <= 8; ++line) 120 | if(mat[line][column] == digit) 121 | exists = 1; 122 | if(exists == 0) 123 | { 124 | int note_line = -1; 125 | int count = 0; 126 | for(int line = 0; line <= 8; ++line) 127 | if(notes[line][column][digit] == 1) 128 | { 129 | ++count; 130 | note_line = line; 131 | } 132 | if(count == 1) 133 | activate_removals(note_line, column, digit); 134 | } 135 | } 136 | } 137 | 138 | void unique_3x3() 139 | { 140 | for(int start_line = 0; start_line <= 6; start_line += 3) 141 | for(int start_column = 0; start_column <= 6; start_column += 3) 142 | for(int digit = 1; digit <= 9; ++digit) 143 | { 144 | bool exists = 0; 145 | for(int line = start_line; line <= start_line + 2; ++line) 146 | for(int column = start_column; column <= start_column + 2; ++column) 147 | if(mat[line][column] == digit) 148 | exists = 1; 149 | if(exists == 0) 150 | { 151 | int note_line = -1; 152 | int note_column = -1; 153 | int count = 0; 154 | for(int line = start_line; line <= start_line + 2; ++line) 155 | for(int column = start_column; column <= start_column + 2; ++column) 156 | if(notes[line][column][digit] == 1) 157 | { 158 | ++count; 159 | note_line = line; 160 | note_column = column; 161 | } 162 | if(count == 1) 163 | activate_removals(note_line, note_column, digit); 164 | } 165 | } 166 | } 167 | 168 | void one_note() 169 | { 170 | for(int line = 0; line <= 8; ++line) 171 | for(int column = 0; column <= 8; ++column) 172 | { 173 | if(mat[line][column] == 0) 174 | { 175 | int cnt_notes = 0; 176 | int who = 0; 177 | for(int digit = 1; digit <= 9; ++digit) 178 | if(notes[line][column][digit] == 1) 179 | { 180 | cnt_notes++; 181 | who = digit; 182 | } 183 | if(cnt_notes == 1) 184 | activate_removals(line, column, who); 185 | } 186 | } 187 | } 188 | 189 | void reduction() 190 | { 191 | for(int start_line = 0; start_line <= 6; start_line += 3) 192 | for(int start_column = 0; start_column <= 6; start_column += 3) 193 | for(int digit = 1; digit <= 9; ++digit) 194 | { 195 | bool exists = 0; 196 | for(int line = start_line; line <= start_line + 2; ++line) 197 | for(int column = start_column; column <= start_column + 2; ++column) 198 | if(mat[line][column] == digit) 199 | exists = 1; 200 | if(exists == 0) 201 | { 202 | int min_line = 10; 203 | int max_line = -1; 204 | int min_column = 10; 205 | int max_column = -1; 206 | for(int line = start_line; line <= start_line + 2; ++line) 207 | for(int column = start_column; column <= start_column + 2; ++column) 208 | if(notes[line][column][digit] == 1) 209 | { 210 | if(line < min_line) 211 | min_line = line; 212 | if(line > max_line) 213 | max_line = line; 214 | if(column < min_column) 215 | min_column = column; 216 | if(column > max_column) 217 | max_column = column; 218 | } 219 | if(min_line == max_line) 220 | { 221 | for(int column = 0; column <= 8; ++column) 222 | if(column < min_column || column > max_column) 223 | { 224 | if(notes[min_line][column][digit]) 225 | stuck = 0; 226 | notes[min_line][column][digit] = 0; 227 | } 228 | } 229 | if(min_column == max_column) 230 | { 231 | for(int line = 0; line <= 8; ++line) 232 | if(line < min_line || line > max_line) 233 | { 234 | if(notes[line][min_column][digit]) 235 | stuck = 0; 236 | notes[line][min_column][digit] = 0; 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | void subset_reduction_3x3() 244 | { 245 | for(int start_line = 0; start_line <= 6; start_line += 3) 246 | for(int start_column = 0; start_column <= 6; start_column += 3) 247 | { 248 | for(int msk = 1; msk < (1<<9); ++msk) 249 | { 250 | bool frq[10] = {0}; 251 | int cnt_columns = 0; 252 | int cnt = 0; 253 | for(int line = start_line; line <= start_line + 2; ++line) 254 | for(int column = start_column; column <= start_column + 2; ++column) 255 | { 256 | if(msk & (1< 0) 470 | ++solved_count; 471 | } 472 | add_notes(); 473 | 474 | while(solved_count < 81 && stuck == 0) 475 | { 476 | stuck = 1; 477 | unique_row(); 478 | unique_column(); 479 | unique_3x3(); 480 | one_note(); 481 | reduction(); 482 | subset_reduction_3x3(); 483 | subset_reduction_1x9(); 484 | subset_reduction_9x1(); 485 | } 486 | 487 | /* 488 | g << "Logical Solver " << solved_count << '\n'; 489 | g << '\n'; 490 | 491 | print_solution(); 492 | 493 | g << '\n'; 494 | */ 495 | 496 | if(solved_count < 81) 497 | { 498 | for(int line = 0; line <= 8; ++line) 499 | for(int column = 0; column <= 8; ++column) 500 | if(mat[line][column] != 0) 501 | org[line][column] = 1; 502 | bkt(0, 0); 503 | } 504 | } 505 | 506 | int main() 507 | { 508 | frq[100] = frq[500] = frq[1000] = frq[2000] = frq[4000] = frq[5000] = frq[8000] = 1; 509 | frq[10000] = frq[20000] = frq[40000] = frq[50000] = 1; 510 | frq[100000] = frq[200000] = frq[400000] = frq[500000] = frq[800000] = frq[1000000] = 1; 511 | 512 | 513 | double time_beginning = clock(); 514 | int t; 515 | f >> t; 516 | for(int grid = 1; grid <= t; ++grid) 517 | { 518 | memset(mat, 0, sizeof(mat)); 519 | memset(notes, 0, sizeof(notes)); 520 | memset(org, 0, sizeof(org)); 521 | stuck = 0; 522 | solved_count = 0; 523 | found = 0; 524 | string data; 525 | f >> data; 526 | int line = 0, column = 0; 527 | for(int pos = 0; pos <= 80; ++pos) 528 | { 529 | if(data[pos] != '.') 530 | mat[line][column] = data[pos] - '0'; 531 | ++column; 532 | if(column == 9) 533 | ++line, column = 0; 534 | } 535 | sudoku_solver(); 536 | bool incorrect = 0; 537 | for(int line = 0; line <= 8; ++line) 538 | for(int column = 0; column <= 8; ++column) 539 | if(mat[line][column] == 0) 540 | incorrect = 1; 541 | if(incorrect) 542 | { 543 | g << grid << '\n'; 544 | print_solution(); 545 | } 546 | if(frq[grid] == 1) // intermediary points used for testing the speed of the algorithm 547 | { 548 | double time_ending = clock(); 549 | double times = time_ending - time_beginning; 550 | times /= CLOCKS_PER_SEC; 551 | g << "set 1: " << times << "s for " << grid << " grids " << '\n'; 552 | } 553 | } 554 | 555 | 556 | return 0; 557 | } 558 | -------------------------------------------------------------------------------- /sudoku.py: -------------------------------------------------------------------------------- 1 | mat = [[0 for x in range(9)] for y in range(9)] 2 | 3 | correct = [[0 for x in range(9)] for y in range(9)] 4 | 5 | notes = [[[0 for z in range(10)] for x in range(9)] for y in range(9)] 6 | 7 | line_borders_3x3 = [[0 for x in range(9)] for y in range(9)] 8 | 9 | column_borders_3x3 = [[0 for x in range(9)] for y in range(9)] 10 | 11 | 12 | def add_notes(): 13 | for line in range(0, 9): 14 | for column in range(0, 9): 15 | if (mat[line][column] == 0): 16 | for digit in range(1, 10): 17 | added = 1; 18 | for pos_line in range(0, 9): 19 | if (mat[pos_line][column] == digit): 20 | added = 0; 21 | for pos_column in range(0, 9): 22 | if (mat[line][pos_column] == digit): 23 | added = 0; 24 | for pos_line in range(line_borders_3x3[line][column], line_borders_3x3[line][column] + 3): 25 | for pos_column in range(column_borders_3x3[line][column], column_borders_3x3[line][column] + 3): 26 | if (mat[pos_line][pos_column] == digit): 27 | added = 0; 28 | notes[line][column][digit] = added 29 | 30 | 31 | stuck = 0 32 | 33 | solved_count = 0 34 | 35 | 36 | def remove_notes_rows(line, digit): 37 | for column in range(0, 9): 38 | notes[line][column][digit] = 0 39 | 40 | 41 | def remove_notes_columns(column, digit): 42 | for line in range(0, 9): 43 | notes[line][column][digit] = 0 44 | 45 | 46 | def remove_notes_3x3(line, column, digit): 47 | for poz_line in range(line, line + 3): 48 | for poz_column in range(column, column + 3): 49 | notes[poz_line][poz_column][digit] = 0 50 | 51 | 52 | def remove_notes_spot(line, column): 53 | for digit in range(1, 10): 54 | notes[line][column][digit] = 0 55 | 56 | 57 | def unique_row(): 58 | global solved_count 59 | global stuck 60 | for line in range(0, 9): 61 | for digit in range(1, 10): 62 | exists = 0 63 | for column in range(0, 9): 64 | if (mat[line][column] == digit): 65 | exists = 1 66 | if (exists == 0): 67 | note_column = -1 68 | count = 0 69 | for column in range(0, 9): 70 | if (notes[line][column][digit] == 1): 71 | count = count + 1 72 | note_column = column 73 | if (count == 1): 74 | solved_count = solved_count + 1 75 | stuck = 0 76 | mat[line][note_column] = digit 77 | remove_notes_spot(line, note_column) 78 | remove_notes_rows(line, digit) 79 | remove_notes_columns(note_column, digit) 80 | remove_notes_3x3(line_borders_3x3[line][note_column], column_borders_3x3[line][note_column], digit) 81 | 82 | 83 | def unique_column(): 84 | global solved_count 85 | global stuck 86 | for column in range(0, 9): 87 | for digit in range(1, 10): 88 | exists = 0 89 | for line in range(0, 9): 90 | if (mat[line][column] == digit): 91 | exists = 1 92 | if (exists == 0): 93 | note_line = -1 94 | count = 0 95 | for line in range(0, 9): 96 | if (notes[line][column][digit] == 1): 97 | ++count; 98 | note_line = line; 99 | if (count == 1): 100 | solved_count = solved_count + 1 101 | stuck = 0 102 | mat[note_line][column] = digit 103 | remove_notes_spot(note_line, column) 104 | remove_notes_rows(note_line, digit) 105 | remove_notes_columns(column, digit) 106 | remove_notes_3x3(line_borders_3x3[note_line][column], column_borders_3x3[note_line][column], digit) 107 | 108 | 109 | def unique_3x3(): 110 | global solved_count 111 | global stuck 112 | for start_line in range(0, 9, 3): 113 | for start_column in range(0, 9, 3): 114 | for digit in range(1, 10): 115 | exists = 0 116 | for line in range(start_line, start_line + 3): 117 | for column in range(start_column, start_column + 3): 118 | if (mat[line][column] == digit): 119 | exists = 1 120 | 121 | if (exists == 0): 122 | note_line = -1 123 | note_column = -1 124 | count = 0 125 | for line in range(start_line, start_line + 3): 126 | for column in range(start_column, start_column + 3): 127 | if (notes[line][column][digit] == 1): 128 | count = count + 1 129 | note_line = line 130 | note_column = column 131 | 132 | if (count == 1): 133 | solved_count = solved_count + 1 134 | ++solved_count 135 | stuck = 0 136 | mat[note_line][note_column] = digit 137 | remove_notes_spot(note_line, note_column) 138 | remove_notes_rows(note_line, digit) 139 | remove_notes_columns(note_column, digit) 140 | remove_notes_3x3(line_borders_3x3[note_line][note_column], 141 | column_borders_3x3[note_line][note_column], digit) 142 | 143 | 144 | def one_note(): 145 | global solved_count 146 | global stuck 147 | for line in range(0, 9): 148 | for column in range(0, 9): 149 | if (mat[line][column] == 0): 150 | cnt_notes = 0 151 | who = 0 152 | for digit in range(1, 10): 153 | if (notes[line][column][digit] == 1): 154 | cnt_notes = cnt_notes + 1 155 | who = digit 156 | if (cnt_notes == 1): 157 | solved_count = solved_count + 1 158 | stuck = 0 159 | mat[line][column] = who 160 | remove_notes_spot(line, column) 161 | remove_notes_rows(line, who) 162 | remove_notes_columns(column, who) 163 | remove_notes_3x3(line_borders_3x3[line][column], column_borders_3x3[line][column], who) 164 | 165 | 166 | def reduction(): 167 | global solved_count 168 | global stuck 169 | for start_line in range(0, 9, 3): 170 | for start_column in range(0, 9, 3): 171 | for digit in range(1, 10): 172 | exists = 0 173 | for line in range(start_line, start_line + 3): 174 | for column in range(start_column, start_column + 3): 175 | if (mat[line][column] == digit): 176 | exists = 1 177 | if (exists == 0): 178 | min_line = 10 179 | max_line = -1 180 | min_column = 10 181 | max_column = -1 182 | for line in range(start_line, start_line + 3): 183 | for column in range(start_column, start_column + 3): 184 | if (notes[line][column][digit] == 1): 185 | if (line < min_line): 186 | min_line = line 187 | if (line > max_line): 188 | max_line = line 189 | if (column < min_column): 190 | min_column = column 191 | if (column > max_column): 192 | max_column = column 193 | if (min_line == max_line): 194 | for column in range(0, 9): 195 | if (column < min_column or column > max_column): 196 | if (notes[min_line][column][digit]): 197 | stuck = 0 198 | notes[min_line][column][digit] = 0 199 | if (min_column == max_column): 200 | for line in range(0, 9): 201 | if (line < min_line or line > max_line): 202 | if (notes[line][min_column][digit]): 203 | stuck = 0 204 | notes[line][min_column][digit] = 0 205 | 206 | 207 | def subset_reduction_3x3(): 208 | global solved_count 209 | global stuck 210 | for start_line in range(0, 9, 3): 211 | for start_column in range(0, 9, 3): 212 | for msk in range(1, 512): 213 | frq = [] 214 | for i in range(0, 10): 215 | frq.append(0) 216 | cnt_columns = 0 217 | cnt = 0; 218 | for line in range(start_line, start_line + 3): 219 | for column in range(start_column, start_column + 3): 220 | if (msk & (2 ** cnt) != 0): 221 | if (mat[line][column] != 0): 222 | cnt_columns = 100 223 | cnt_columns = cnt_columns + 1 224 | for digit in range(1, 10): 225 | if (notes[line][column][digit] == 1): 226 | frq[digit] = 1 227 | cnt = cnt + 1 228 | cnt_digits = 0 229 | for digit in range(1, 10): 230 | if (frq[digit] == 1): 231 | cnt_digits = cnt_digits + 1 232 | if (cnt_digits == cnt_columns): 233 | cnt = 0 234 | for line in range(start_line, start_line + 3): 235 | for column in range(start_column, start_column + 3): 236 | if ((msk & (2 ** cnt)) == 0): 237 | for digit in range(1, 10): 238 | if (frq[digit] == 1): 239 | if (notes[line][column][digit] == 1): 240 | notes[line][column][digit] = 0 241 | stuck = 0 242 | cnt = cnt + 1 243 | 244 | 245 | def subset_reduction_1x9(): 246 | global solved_count 247 | global stuck 248 | for line in range(0, 9): 249 | for msk in range(1, 512): 250 | frq = [] 251 | for i in range(0, 10): 252 | frq.append(0) 253 | cnt_columns = 0 254 | for column in range(0, 9): 255 | if ((msk & (2 ** column)) != 0): 256 | if (mat[line][column] != 0): 257 | cnt_columns = 100 258 | ++cnt_columns 259 | for digit in range(1, 10): 260 | if (notes[line][column][digit] == 1): 261 | frq[digit] = 1 262 | cnt_digits = 0 263 | for digit in range(1, 10): 264 | if (frq[digit] == 1): 265 | cnt_digits = cnt_digits + 1 266 | if (cnt_digits == cnt_columns): 267 | for column in range(0, 9): 268 | if ((msk & (2 ** column)) == 0): 269 | for digit in range(1, 10): 270 | if (frq[digit] == 1): 271 | if (notes[line][column][digit] == 1): 272 | notes[line][column][digit] = 0 273 | stuck = 0 274 | 275 | 276 | def subset_reduction_9x1(): 277 | global solved_count 278 | global stuck 279 | for column in range(0, 8): 280 | for msk in range(1, 512): 281 | frq = [] 282 | for i in range(0, 10): 283 | frq.append(0) 284 | cnt_lines = 0 285 | for line in range(0, 9): 286 | if ((msk & (2 ** line) != 0)): 287 | if (mat[line][column] != 0): 288 | cnt_lines = 100 289 | cnt_lines = cnt_lines + 1 290 | for digit in range(1, 10): 291 | if (notes[line][column][digit] == 1): 292 | frq[digit] = 1 293 | cnt_digits = 0 294 | for digit in range(1, 10): 295 | if (frq[digit] == 1): 296 | cnt_digits = cnt_digits + 1 297 | if (cnt_digits == cnt_lines): 298 | for line in range(0, 9): 299 | if ((msk & (2 ** line)) == 0): 300 | for digit in range(1, 10): 301 | if (frq[digit] == 1): 302 | if (notes[line][column][digit] == 1): 303 | notes[line][column][digit] = 0 304 | stuck = 0 305 | 306 | 307 | org = [[0 for x in range(9)] for y in range(9)] 308 | 309 | found = 0 310 | 311 | 312 | def bkt(L, C): 313 | global found 314 | if (found == 0): 315 | if (L == 9): 316 | found = 1 317 | return 318 | if (L != 9): 319 | if (mat[L][C] != 0): 320 | if (C == 8): 321 | bkt(L + 1, 0) 322 | else: 323 | bkt(L, C + 1) 324 | else: 325 | for digit in range(1, 10): 326 | if (notes[L][C][digit] == 1): 327 | added = 1 328 | for pos_line in range(0, 9): 329 | if (mat[pos_line][C] == digit): 330 | added = 0 331 | for pos_column in range(0, 9): 332 | if (mat[L][pos_column] == digit): 333 | added = 0 334 | for pos_line in range(line_borders_3x3[L][C], line_borders_3x3[L][C] + 3): 335 | for pos_column in range(column_borders_3x3[L][C], column_borders_3x3[L][C] + 3): 336 | if (mat[pos_line][pos_column] == digit): 337 | added = 0 338 | if (added == 1): 339 | mat[L][C] = digit 340 | if (C == 8): 341 | bkt(L + 1, 0) 342 | else: 343 | bkt(L, C + 1) 344 | if (found): 345 | return 346 | mat[L][C] = 0 347 | if (found == 1): 348 | return; 349 | 350 | 351 | def sudoku_solver(): 352 | global solved_count 353 | global stuck 354 | for line in range(0, 9): 355 | for column in range(0, 9): 356 | line_borders_3x3[line][column] = (line) // 3 * 3 357 | column_borders_3x3[line][column] = (column // 3) * 3 358 | if (mat[line][column] > 0): 359 | solved_count = solved_count + 1 360 | 361 | add_notes() 362 | 363 | while (solved_count < 81 and stuck == 0): 364 | stuck = 1 365 | unique_row() 366 | unique_column() 367 | unique_3x3() 368 | one_note() 369 | reduction() 370 | subset_reduction_3x3() 371 | subset_reduction_1x9() 372 | subset_reduction_9x1() 373 | 374 | if (solved_count < 81): 375 | for line in range(0, 9): 376 | for column in range(0, 9): 377 | if (mat[line][column] != 0): 378 | org[line][column] = 1 379 | bkt(0, 0) 380 | 381 | f = open("input.in", "r") 382 | t = int(f.readline()) 383 | for grid in range(0, t): 384 | for line in range(0, 9): 385 | for column in range(0, 9): 386 | mat[line][column] = 0 387 | org[line][column] = 0 388 | for digit in range(0, 10): 389 | notes[line][column][digit] = 0 390 | data = f.readline() 391 | stuck = 0 392 | solved_count = 0 393 | found = 0 394 | line = 0 395 | column = 0 396 | for pos in range(0, 81): 397 | if (data[pos] != '.'): 398 | mat[line][column] = int(data[pos]) 399 | else: 400 | mat[line][column] = 0 401 | column = column + 1 402 | if (column == 9): 403 | line = line + 1 404 | column = 0 405 | 406 | sudoku_solver() 407 | --------------------------------------------------------------------------------