├── solved1.png ├── solver.png ├── test1.png ├── detected1.png ├── LICENSE ├── README.md ├── Solver.py ├── CreatingSudokuSolverModels.py ├── FindSud.py └── Functions.py /solved1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AryaKoureshi/SolSudo/HEAD/solved1.png -------------------------------------------------------------------------------- /solver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AryaKoureshi/SolSudo/HEAD/solver.png -------------------------------------------------------------------------------- /test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AryaKoureshi/SolSudo/HEAD/test1.png -------------------------------------------------------------------------------- /detected1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AryaKoureshi/SolSudo/HEAD/detected1.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Arya 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 | ## Topic 4 | Sudoku solver (SolSudo) 5 | 6 | ## Abstract 7 | SolSudo is a sudoku solver that works with deep learning. SolSudo can solves sudokus by using images and it has an intelligence method. According to this method, the model predicts the blank digits, and when each level is completed, blanks are filled one after another. Each time a blank is filled, new sudoku will be fed to the solver to detect the next digit. Again and again, until there isn't a blank left. One of the features of this project is detecting sudoku from an image and filling in the blanks that require tesseract-ocr, however, which may cause problems. Therefore, I devised a method as in sudoku numbers are entered one by one, and 0 is used for blanks. 8 | 9 | ## Links 10 | [Github](https://github.com/AryaKoureshi/SolSudo) 11 | 12 | [Linkedin](https://www.linkedin.com/posts/arya-koureshi_deeplearning-python-tensorflow-activity-6711641409658716160-kdSD) 13 | 14 | ## Result 15 | #### Test data 16 | ![Test data](test1.png) 17 | 18 | #### Detected sudoku 19 | ![Detected Sudoku](detected1.png) 20 | 21 | #### Solved sudoku 22 | ![Solved Sudoku](solved1.png) 23 | 24 | -------------------------------------------------------------------------------- /Solver.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | import numpy as np 3 | from tensorflow.keras.models import load_model 4 | from Functions import smart_solver, checker 5 | from FindSud import finder_from_image, create_solved_sudoku, finder_from_text 6 | #::::::::::::::::::::::::::::::::::::::::::::::: 7 | # Finding Sudoku 8 | print("Finding Sudoku...") 9 | key = int(input("What you want?\n 1.with Text\n 2.with Image (need Tesseract)\n Enter the number : ")) 10 | while True: 11 | if key == 1: 12 | entered_sudoku = input("Please enter the sudoku with 0 for blank digits : ") 13 | entered_sudoku = str(entered_sudoku) 14 | finded_sudoku = finder_from_text(entered_sudoku) 15 | path_of_sudoku = False 16 | break 17 | elif key == 2: 18 | path_of_sudoku = input("Please enter the path of sudoku (with */* and file name): ") 19 | path_of_sudoku = str(path_of_sudoku) 20 | finded_sudoku = finder_from_image(path_of_sudoku) 21 | print("Find the Sudoku was successful.") 22 | break 23 | else: 24 | print("Try Again!") 25 | key = int(input("What you want? 1 or 2 ??")) 26 | #::::::::::::::::::::::::::::::::::::::::::::::: 27 | # Load Model and Predict the Sudoku 28 | print("Loading Model...") 29 | path_of_model = input("Please enter the path of Model (with */* and model name): ") 30 | path_of_model = str(path_of_model) 31 | solver = load_model(path_of_model) 32 | solver.compile( 33 | optimizer='adam', 34 | loss='categorical_crossentropy', 35 | metrics=['accuracy'] 36 | ) 37 | print("Loading Model was successful.") 38 | #predicting 39 | print("Solving Sudoku...") 40 | solved = smart_solver(np.reshape(finded_sudoku, (1, 9, 9)), solver) 41 | print("Sudoku Solved. \nChecking...") 42 | checked = checker(np.reshape(solved, (1, 9, 9))) 43 | print("Cheked successful.") 44 | if checked == True : 45 | print("Solved the Sudoku was Right!") 46 | print("Show solved Sudoku rightly...") 47 | if path_of_sudoku == False : print(solved) 48 | else : create_solved_sudoku(path_of_sudoku, solved.T) 49 | else : 50 | print("I'm sorry, I couldn't solve this sudoku rightly! You can create your Model with 'CreatingSudokuSolverModels.py' better than this Model.") 51 | print("Show solved Sudoku wrongly...") 52 | if path_of_sudoku == False : print(solved) 53 | else : create_solved_sudoku(path_of_sudoku, solved.T) 54 | 55 | -------------------------------------------------------------------------------- /CreatingSudokuSolverModels.py: -------------------------------------------------------------------------------- 1 | # imports 2 | from tensorflow.keras import Model, Sequential 3 | from tensorflow.keras.callbacks import EarlyStopping 4 | from tensorflow.keras.layers import Dense, Dropout, Flatten, Input 5 | from tensorflow.keras.utils import to_categorical 6 | from tensorflow.keras.utils import plot_model 7 | from Functions import load_data, delete_digits, batch_smart_solver 8 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 9 | # Prepare data 10 | # we creating our Xtrain data from Ytrain data with delete_digits function 11 | (_, Ytrain), (Xtest, Ytest) = load_data(nb_train=60000, nb_test=1) # We won't use _. We will work directly with Ytrain 12 | # one-hot-encoding --> shapes become : 13 | # (?, 9, 9, 10) for Xs ____ because in Xtrain data we have 0 for blanks digits 14 | # (?, 9, 9, 9) for Ys ____ but in Ytrain data we don't have 0 15 | Xtrain = to_categorical(Ytrain).astype('float32') # from Ytrain cause we will creates quizzes from solusions 16 | Xtest = to_categorical(Xtest).astype('float32') 17 | Ytrain = to_categorical(Ytrain-1).astype('float32') # (y-1) because we 18 | Ytest = to_categorical(Ytest-1).astype('float32') # don't want to predict zeros 19 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 20 | # Creating model 21 | # input_shape is (9,9,10) because we have sudokus with shape (9,9) that were the categorical 22 | input_shape = (9, 9, 10) 23 | # first layer 24 | grid = Input(shape=input_shape) # inputs 25 | # creating costomized model 26 | model = Sequential() 27 | model.add(Dense(64, activation='relu', input_shape=input_shape)) 28 | model.add(Dropout(0.4)) 29 | model.add(Dense(64, activation='relu')) 30 | model.add(Dropout(0.4)) 31 | model.add(Dense(64, activation='relu')) 32 | model.add(Dropout(0.4)) 33 | model.add(Flatten()) 34 | # grid is a layer and in this code, we attaching the created model to the grid 35 | features = model(grid) # commons features 36 | # define one Dense layer for each of the digit we want to predict 37 | # 81 layers atached to the model 38 | digit_placeholders = [ 39 | Dense(9, activation='softmax')(features) 40 | for i in range(81) 41 | ] 42 | # creating final model 43 | solver = Model(grid, digit_placeholders) # build the whole model 44 | # compiling created model 45 | solver.compile( 46 | optimizer='adam', 47 | loss='categorical_crossentropy', 48 | metrics=['accuracy'] 49 | ) 50 | # grid ---> model ---> degit_placeholders 51 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 52 | # Train Model 53 | 54 | # First train 55 | # in the firs training we don't delete any digit 56 | solver.fit( 57 | delete_digits(Xtrain, 0), # we don't delete any digit for now 58 | [Ytrain[:, i, j, :] for i in range(9) for j in range(9)], # each digit of solution 59 | batch_size=128, 60 | epochs=1, # 1 epoch should be enough for the task 61 | verbose=1, 62 | ) 63 | 64 | # Second train 65 | early_stop = EarlyStopping(patience=2, verbose=1) 66 | i = 1 67 | for nb_epochs, nb_delete in zip( 68 | [5, 10, 10],#[1, 2, 3, 4, 6, 8, 10, 10, 10, 10, 10, 15, 15, 15, 15, 15, 15, 20, 25, 30], # epochs for each round 69 | [20, 55, 58] #[1, 2, 3, 4, 6, 8, 10, 12, 14, 17, 20, 23, 25, 30, 35, 40, 45, 50, 55, 60] # digit to pull off 70 | ): 71 | print('Pass n° {} ...'.format(i)) 72 | i += 1 73 | 74 | solver.fit( 75 | delete_digits(Xtrain, nb_delete), # delete digits from training sample 76 | [Ytrain[:, i, j, :] for i in range(9) for j in range(9)], 77 | validation_batch_size=0.01, 78 | shuffle=True, 79 | batch_size=128, 80 | epochs=nb_epochs, 81 | verbose=1, 82 | callbacks=[early_stop] 83 | ) 84 | print("Saving trained model..") 85 | solver.save('Enter Location!/solver.h5') 86 | print("Saved.") 87 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 88 | # Predicting 89 | quizzes = Xtest.argmax(3) # quizzes in the (?, 9, 9) shape. From the test set 90 | true_grids = Ytest.argmax(3) + 1 # true solutions dont forget to add 1 91 | smart_guesses = batch_smart_solver(quizzes, solver) # make smart guesses ! 92 | print("Saving trained model..") 93 | solver.save('Enter Location!/solver.h5') 94 | print("Saved.") 95 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 96 | # Plot model 97 | print("Ploting Model...") 98 | plot_model(solver, to_file='Enter Location!/solver.png', show_shapes=True, expand_nested=True, dpi=300) 99 | print("Plotted Successful.") 100 | print("All Process Passed Successful!") 101 | -------------------------------------------------------------------------------- /FindSud.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | import cv2 3 | import numpy as np 4 | import pytesseract 5 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 6 | def finder_from_image(file_path): 7 | """ 8 | this function used for finding the sudoku from image 9 | 10 | Parameter 11 | --------- 12 | file_path : wiht */* and file name 13 | 14 | Returns 15 | ------- 16 | findedSudoku : shape(9, 9) 17 | 18 | """ 19 | # Image Initializing 20 | image = cv2.imread(file_path) 21 | image2 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 22 | _, image2 = cv2.threshold(image2, 200, 255, cv2.THRESH_BINARY) 23 | edge = cv2.Canny(image2, 50, 100) 24 | y1,x1 = np.argwhere(edge).min(axis=0) 25 | y2,x2 = np.argwhere(edge).max(axis=0) 26 | cropped = image2[y1:y2, x1:x2] 27 | ycell = int(np.shape(cropped)[0]/9) 28 | xcell = int(np.shape(cropped)[1]/9) 29 | # Finding numbers and writing them into a string 30 | findedNumbers = "" 31 | for i in range(9): 32 | for k in range(9): 33 | selected = cropped[(i*ycell)+((y2-y1)//57):((i+1)*ycell)-((y2-y1)//57), 34 | (k*xcell)+((x2-x1)//57):((k+1)*xcell)-((x2-x1)//57)] 35 | copyfindedNumbers = findedNumbers 36 | findedNumbers += pytesseract.image_to_string(selected, lang='eng', config='--psm 10 --oem 3 -c tessedit_char_whitelist=123456789') 37 | findedNumbers = findedNumbers.replace("\n", "") 38 | findedNumbers = findedNumbers.replace("\x0c", "") 39 | if findedNumbers == copyfindedNumbers : findedNumbers += "0" 40 | # Show detected sudoku and finded numbers 41 | tagged = cv2.rectangle(image, (x1,y1), (x2,y2), (0,255,0), 3, cv2.LINE_AA) 42 | #cv2.imshow('Detected Sudoku',tagged) 43 | findedSudoku = np.array(np.reshape(list(int(i) for i in findedNumbers), (9, 9))) 44 | cv2.imwrite('detected.png', tagged) 45 | cv2.waitKey(0) 46 | cv2.destroyAllWindows() 47 | return findedSudoku 48 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 49 | def create_solved_sudoku(unsolved_sudoku_path, solved_sudoku): 50 | """ 51 | this function used for showing the solved sudoku on image 52 | 53 | Parameter 54 | --------- 55 | unsolved_sudoku_path : the path of unsolved sudoku with */* and file name 56 | solved_sudoku : shape (9, 9) --> received from predicted sudoku by model 57 | 58 | Returns 59 | ------- 60 | show_solved_sudoku : shape(9, 9) 61 | 62 | """ 63 | # Image Initializing 64 | show_solved_sudoku = cv2.imread(unsolved_sudoku_path) 65 | show_solved_sudoku2 = cv2.cvtColor(show_solved_sudoku, cv2.COLOR_BGR2GRAY) 66 | _, image2 = cv2.threshold(show_solved_sudoku2, 200, 255, cv2.THRESH_BINARY) 67 | edge = cv2.Canny(show_solved_sudoku2, 50, 100) 68 | y1,x1 = np.argwhere(edge).min(axis=0) 69 | y2,x2 = np.argwhere(edge).max(axis=0) 70 | cropped = show_solved_sudoku2[y1:y2, x1:x2] 71 | ycell = int(np.shape(cropped)[0]/9) 72 | xcell = int(np.shape(cropped)[1]/9) 73 | for i in range(9): 74 | for k in range(9): 75 | selected = cropped[(i*ycell)+((y2-y1)//55):((i+1)*ycell)-((y2-y1)//55), 76 | (k*xcell)+((x2-x1)//55):((k+1)*xcell)-((x2-x1)//55)] 77 | xx = int(np.shape(selected)[0]) 78 | yy = int(np.shape(selected)[1]) 79 | if ((list(np.reshape(selected, (xx * yy)))).count(255)) >= 0.95 * (xx * yy) : 80 | cv2.putText(show_solved_sudoku[y1 + (i*ycell):y1 + ((i+1)*ycell), 81 | x1 + (k*xcell):x1 + ((k+1)*xcell)], 82 | '{}'.format(solved_sudoku[i][k]), 83 | (int(xcell/3.333), int(ycell/1.333)), 84 | cv2.FONT_HERSHEY_SIMPLEX, 85 | xx/32, 86 | (0,0,255), 87 | int(xx/16)) 88 | # Show solved sudoku 89 | show_solved_sudoku = cv2.rectangle(show_solved_sudoku, 90 | (x1,y1), (x2,y2), 91 | (0,255,0), 92 | 3, 93 | cv2.LINE_AA) 94 | cv2.imwrite('solved.png', show_solved_sudoku) 95 | cv2.imshow('Solved Sudoku!',show_solved_sudoku) 96 | cv2.waitKey(0) 97 | cv2.destroyAllWindows() 98 | return print(":)") 99 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 100 | # From Text 101 | def finder_from_text(str_sudoku): 102 | """ 103 | this function used for find sudoku from inputed string 104 | 105 | Parameter 106 | --------- 107 | str_sudoku: str 108 | 109 | Returns 110 | ------- 111 | sudoku : shape(9, 9) 112 | 113 | """ 114 | sudoku = np.array(np.reshape(list(int(i) for i in str_sudoku), (9, 9))) 115 | return sudoku 116 | 117 | -------------------------------------------------------------------------------- /Functions.py: -------------------------------------------------------------------------------- 1 | ### Imports 2 | import pandas as pd 3 | import numpy as np 4 | from tensorflow.keras.utils import to_categorical 5 | ### Definition functions 6 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 7 | # Function for loading data 8 | def load_data(nb_train=40000, nb_test=10000, full=False): 9 | """ 10 | Function to load data in the keras way. 11 | 12 | Parameters 13 | ---------- 14 | nb_train (int): Number of training examples 15 | nb_test (int): Number of testing examples 16 | full (bool): If True, whole csv will be loaded, we dont have nb_test 17 | all of the data using for training 18 | 19 | Returns 20 | ------- 21 | Xtrain, ytrain (np.array, np.array), 22 | shapes (nb_train, 9, 9), (nb_train, 9, 9): Training samples 23 | Xtest, ytest (np.array, np.array), 24 | shapes (nb_test, 9, 9), (nb_test, 9, 9): Testing samples 25 | """ 26 | # if full is true, load the whole dataset 27 | if full: 28 | sudokus = pd.read_csv('Enter Location!/sudoku.csv').values 29 | # Transpose of sudokus matrix to quizzes and solutions 30 | quizzes, solutions = sudokus.T 31 | # Create sudoku shape from quizzes 32 | Xs = np.array([np.reshape([int(element) for element in sud], (9, 9)) 33 | for sud in quizzes]) 34 | # Create sudoku shape from solutions 35 | Ys = np.array([np.reshape([int(element) for element in sud], (9, 9)) 36 | for sud in solutions]) 37 | return (Xs[:nb_train], Ys[:nb_train]) 38 | else: 39 | sudokus = next( 40 | pd.read_csv('Enter Location!/sudoku.csv', chunksize=(nb_train + nb_test)) 41 | ).values 42 | # Transpose of sudokus matrix to quizzes and solutions 43 | quizzes, solutions = sudokus.T 44 | # Create sudoku shape from quizzes 45 | Xs = np.array([np.reshape([int(element) for element in sud], (9, 9)) 46 | for sud in quizzes]) 47 | # Create sudoku shape from solutions 48 | Ys = np.array([np.reshape([int(element) for element in sud], (9, 9)) 49 | for sud in solutions]) 50 | return (Xs[:nb_train], Ys[:nb_train]), (Xs[nb_train:], Ys[nb_train:]) 51 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 52 | # Function for deleting random digits from sudoku 53 | def delete_digits(X, n_delet=1): 54 | """ 55 | This function is used to create sudoku quizzes from solutions 56 | 57 | Parameters 58 | ---------- 59 | X (np.array), shape (?, 9, 9, 9|10): input solutions grids. 60 | n_delet (integer): max number of digit to suppress from original solutions 61 | 62 | Returns 63 | ------- 64 | grids: np.array of grids to guess in one-hot way. Shape: (?, 9, 9, 10) 65 | """ 66 | # argmax function can convert categorical shape to sudoku shape 67 | grids = X.argmax(3) # get the grid in a (9, 9) integer shape 68 | # this 'for' generates blanks with n_delet size 69 | # this works randomly and give sudoku shape 70 | for grid in grids: 71 | grid.flat[np.random.randint(0, 81, n_delet)] = 0 # generate blanks (replace = True) 72 | # and the end return the categorical shape of generated sudoku 73 | return to_categorical(grids) 74 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 75 | # Function for solving smartly 76 | def batch_smart_solver(grids, solver): 77 | """ 78 | NOTE : This function is ugly, feel free to optimize the code ... 79 | 80 | This function solves quizzes in the "smart" way. 81 | It will fill blanks one after the other. Each time a digit is filled, 82 | the new grid will be fed again to the solver to predict the next digit. 83 | Again and again, until there is no more blank. 84 | 85 | Parameters 86 | ---------- 87 | grids (np.array), shape (?, 9, 9): Batch of quizzes to solve (smartly ;)) 88 | solver (tensorflow.keras.model): The neural net solver 89 | 90 | Returns 91 | ------- 92 | grids (np.array), shape (?, 9, 9): Smartly solved quizzes. 93 | """ 94 | # getting work with copy of grids 95 | grids = grids.copy() 96 | # (grids == 0).sum((1, 2))--> number of zeros 97 | for _ in range((grids == 0).sum((1, 2)).max()): 98 | preds = np.array(solver.predict(to_categorical(grids))) # get predictions 99 | probs = preds.max(2).T # get highest probability for each 81 digit to predict 100 | values = preds.argmax(2).T + 1 # get corresponding values 101 | zeros = (grids == 0).reshape((grids.shape[0], 81)) # get blank positions 102 | 103 | for grid, prob, value, zero in zip(grids, probs, values, zeros): 104 | if any(zero): # don't try to fill already completed grid 105 | where = np.where(zero)[0] # focus on blanks only 106 | confidence_position = where[prob[zero].argmax()] # best score FOR A ZERO VALUE (confident blank) 107 | confidence_value = value[confidence_position] # get corresponding value 108 | grid.flat[confidence_position] = confidence_value # fill digit inplace 109 | return grids 110 | 111 | def smart_solver(grid, solver): 112 | """ 113 | NOTE : This function is ugly, feel free to optimize the code ... 114 | 115 | This function solves quiz in the "smart" way. 116 | It will fill blanks one after the other. Each time a digit is filled, 117 | the new grid will be fed again to the solver to predict the next digit. 118 | Again and again, until there is no more blank. 119 | 120 | Parameters 121 | ---------- 122 | grid (np.array), shape (1, 9, 9) 123 | solver (tensorflow.keras.model): The neural net solver 124 | 125 | Returns 126 | ------- 127 | grid (np.array), shape (9, 9): Smartly solved quiz. 128 | """ 129 | # getting work with copy of grids 130 | grid = grid.copy() 131 | # (grids == 0).sum((1, 2))--> number of zeros 132 | for _ in range((grid == 0).sum().max()): 133 | preds = np.array(solver.predict(to_categorical(np.reshape(grid, (1, 9, 9))))) # get predictions 134 | probs = preds.max(2).T # get highest probability for each 81 digit to predict 135 | values = preds.argmax(2).T + 1 # get corresponding values 136 | zeros = (grid == 0).reshape((1, 81)) # get blank positions 137 | for prob, value, zero in zip(probs, values, zeros): 138 | if any(zero): # don't try to fill already completed grid 139 | where = np.where(zero)[0] # focus on blanks only 140 | confidence_position = where[prob[zero].argmax()] # best score FOR A ZERO VALUE (confident blank) 141 | confidence_value = value[confidence_position] # get corresponding value 142 | grid.flat[confidence_position] = confidence_value # fill digit inplace 143 | return np.reshape(grid, (9,9)) 144 | #:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 145 | # Function for checking sudoku 146 | def batch_checker(predicted_quizzes): 147 | """ 148 | This function checks the batch of quizzes for that those are right or wrong. 149 | 150 | Parameter 151 | --------- 152 | predicted_quizzes (np.array), shape (?, 9, 9) 153 | 154 | Return 155 | ------ 156 | checked_quizzes : list of True or False for each quiz in predicted_quizzes 157 | """ 158 | predicted_quizzes.copy() 159 | checked_quizzes = [] 160 | for quiz in predicted_quizzes: 161 | right = True 162 | for i in range(9): 163 | for j in range(9): 164 | if (list(quiz[i])).count(j+1) == 2 : 165 | right = False 166 | break 167 | else: 168 | quiz = quiz.T 169 | if (list(quiz[i])).count(j+1) == 2 : 170 | right = False 171 | break 172 | if right == False: break 173 | checked_quizzes.append(right) 174 | return checked_quizzes 175 | 176 | def checker(predicted_quiz): 177 | """ 178 | This function checks the quiz for that it is right or wrong. 179 | 180 | Parameter 181 | --------- 182 | predicted_quiz (np.array), shape (1, 9, 9) 183 | 184 | Return 185 | ------ 186 | True : sudoku solved rightly. 187 | False : sudoku solved wrongly. 188 | """ 189 | predicted_quiz.copy() 190 | for i in range(9): 191 | for j in range(9): 192 | if (list(predicted_quiz[0][i])).count(j+1) == 2 : return False 193 | else: 194 | predicted_quiz[0] = predicted_quiz[0].T 195 | if (list(predicted_quiz[0][i])).count(j+1) == 2 : return False 196 | 197 | return True 198 | --------------------------------------------------------------------------------