├── README.md ├── codes ├── __pycache__ │ ├── box_line.cpython-38.pyc │ ├── candidate_handler.cpython-38.pyc │ ├── cells_seen.cpython-38.pyc │ ├── hidden_pairs_triples_quads.cpython-38.pyc │ ├── hidden_singles.cpython-38.pyc │ ├── init_board.cpython-38.pyc │ ├── naked_pairs_triples_quads.cpython-38.pyc │ ├── pointing_pairs.cpython-38.pyc │ ├── print_board.cpython-38.pyc │ ├── select_group.cpython-38.pyc │ ├── single_candidate.cpython-38.pyc │ ├── singles_chains.cpython-38.pyc │ ├── solver.cpython-38.pyc │ ├── swordfish.cpython-38.pyc │ ├── validation_check.cpython-38.pyc │ ├── x_wing.cpython-38.pyc │ ├── xyz_wing.cpython-38.pyc │ └── y_wing.cpython-38.pyc ├── box_line.py ├── candidate_handler.py ├── cells_seen.py ├── hidden_pairs_triples_quads.py ├── hidden_singles.py ├── init_board.py ├── naked_pairs_triples_quads.py ├── naked_quads.py ├── not used │ ├── for_swordfish.sav │ ├── for_xyz_wing.sav │ ├── for_xyz_wing2.sav │ ├── hidden_remove.py │ ├── hidden_triples.py │ ├── naked_pairs.py │ ├── naked_remove.py │ └── naked_triples.py ├── pointing_pairs.py ├── print_board.py ├── select_group.py ├── single_candidate.py ├── singles_chains.py ├── solver.py ├── sudoku_solver.py ├── swordfish.py ├── validation_check.py ├── x_wing.py ├── xyz_wing.py └── y_wing.py └── images ├── board_final.JPG ├── board_initial.JPG ├── board_input string.JPG └── candidates.JPG /README.md: -------------------------------------------------------------------------------- 1 | # Sudoku-Solver 2 | Solves the user entered Sudoku boards with commonly known strategies* which are: 3 | - Single candidates 4 | - Hidden singles 5 | - Naked pairs-triples-quads 6 | - Hidden pairs-triples-quads 7 | - Pointing pairs-triples 8 | - Box/Line reduction 9 | - X-wing 10 | - Y-wing 11 | - Simple colouring (Singles' chains) 12 | - XYZ-wing 13 | - Swordfish 14 | 15 | _(*)Strategies are not complete yet, new ones will be added one by one once completed._ 16 | 17 | User enters the board in 81 digit string format where zeros represents empty cells in the board: 18 | ``` python 19 | grid = "000000700000001080300020004090002060005000800080700050200070003060500000003000000" 20 | ``` 21 | 22 | Scrpit converts the string into 9x9 dataframe: 23 | 24 | ![plot2](https://github.com/omerfarukeker/Sudoku-Solver/blob/master/images/board_initial.JPG) 25 | 26 | Candidates for cells are kept in a separate dataframe: 27 | 28 | ![plot4](https://github.com/omerfarukeker/Sudoku-Solver/blob/master/images/candidates.JPG) 29 | 30 | Strategies are implemented in the listed order which is given above. Any changes in the board or candidates results in recursively calling the solver function again. Changes in the board are logged and displayed in Row&Column pair notation: 31 | 32 | ```python 33 | R4C5=5 : Hidden Singles (row) 34 | R5C2=3 : Hidden Singles (col) 35 | R9C4=2 : Hidden Singles (col) 36 | R3C6=7 : Hidden Singles (col) 37 | R1C8=3 : Hidden Singles (col) 38 | R1C6=5 : Hidden Singles (square) 39 | R6C3=2 : Hidden Singles (square) 40 | R4C4=8 : Hidden Singles (square) 41 | R3C3=8 : Hidden Singles (row) 42 | R4C7=3 : Hidden Singles (row) 43 | R7C6=8 : Hidden Singles (row) 44 | R2C4=3 : Hidden Singles (col) 45 | R1C5=8 : Hidden Singles (col) 46 | R0C0 Pointing Pairs (cols), 6 removed 47 | R1C0 Pointing Pairs (cols), 6 removed 48 | R1C1 X-Wing, removed 5 from rows 49 | R8C1 X-Wing, removed 5 from rows 50 | R1C6 X-Wing, removed 5 from rows 51 | R8C6 X-Wing, removed 5 from rows 52 | R0C3 X-Wing, removed 6 from rows 53 | R4C3 X-Wing, removed 6 from rows 54 | R1C6 X-Wing, removed 6 from rows 55 | R8C6 X-Wing, removed 6 from rows 56 | R8C0 Hidden Pairs (row), 1 removed 57 | R8C0 Hidden Pairs (row), 4 removed 58 | R8C0 Hidden Pairs (row), 7 removed 59 | R8C0 Hidden Pairs (row), 9 removed 60 | R8C8 Hidden Pairs (row), 1 removed 61 | R8C8 Hidden Pairs (row), 6 removed 62 | R8C8 Hidden Pairs (row), 7 removed 63 | R8C8 Hidden Pairs (row), 9 removed 64 | R2C6 Hidden Pairs (col), 1 removed 65 | R2C6 Hidden Pairs (col), 9 removed 66 | R6C6 Hidden Pairs (col), 1 removed 67 | R6C6 Hidden Pairs (col), 4 removed 68 | R6C6 Hidden Pairs (col), 9 removed 69 | R7C7=6 : Hidden Singles (square) 70 | R3C7=5 : Single Candidate 71 | R3C2=1 : Single Candidate 72 | R3C8=9 : Single Candidate 73 | R2C7=2 : Single Candidate 74 | R2C9=6 : Single Candidate 75 | R3C4=6 : Single Candidate 76 | R1C9=1 : Single Candidate 77 | R4C9=7 : Single Candidate 78 | R6C9=9 : Single Candidate 79 | R5C9=2 : Single Candidate 80 | R8C9=8 : Single Candidate 81 | R9C9=5 : Single Candidate 82 | R9C1=8 : Single Candidate 83 | R1C3=6 : Hidden Singles (row) 84 | R1C2=2 : Hidden Singles (row) 85 | R2C1=5 : Hidden Singles (row) 86 | R5C1=7 : Hidden Singles (row) 87 | R7C2=5 : Hidden Singles (row) 88 | R8C8=2 : Hidden Singles (row) 89 | R6C1=6 : Hidden Singles (col) 90 | R9C8=7 : Hidden Singles (col) 91 | R8C3=7 : Hidden Singles (square) 92 | R9C2=4 : Single Candidate 93 | R2C2=7 : Single Candidate 94 | R4C4 X-Wing, removed 1 from rows 95 | R7C4 X-Wing, removed 1 from rows 96 | R7C6 X-Wing, removed 1 from rows 97 | R2C6 X-Wing, removed 1 from cols 98 | R7C3=9 : Single Candidate 99 | R8C1=1 : Single Candidate 100 | R2C3=4 : Single Candidate 101 | R2C5=9 : Single Candidate 102 | R4C1=4 : Single Candidate 103 | R4C3=1 : Single Candidate 104 | R1C1=9 : Single Candidate 105 | R1C4=4 : Single Candidate 106 | R7C4=1 : Single Candidate 107 | R7C8=4 : Single Candidate 108 | R8C7=9 : Single Candidate 109 | R9C5=6 : Single Candidate 110 | R9C6=9 : Single Candidate 111 | R9C7=1 : Single Candidate 112 | R5C4=9 : Single Candidate 113 | R5C5=4 : Single Candidate 114 | R5C6=6 : Single Candidate 115 | R5C8=1 : Single Candidate 116 | R6C6=3 : Single Candidate 117 | R6C7=4 : Single Candidate 118 | R8C5=3 : Single Candidate 119 | R8C6=4 : Single Candidate 120 | R6C5=1 : Single Candidate 121 | ``` 122 | 123 | After solving the board, it shows the final look of it and how much time it took: 124 | 125 | ![plot3](https://github.com/omerfarukeker/Sudoku-Solver/blob/master/images/board_final.JPG) 126 | -------------------------------------------------------------------------------- /codes/__pycache__/box_line.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/box_line.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/candidate_handler.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/candidate_handler.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/cells_seen.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/cells_seen.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/hidden_pairs_triples_quads.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/hidden_pairs_triples_quads.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/hidden_singles.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/hidden_singles.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/init_board.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/init_board.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/naked_pairs_triples_quads.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/naked_pairs_triples_quads.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/pointing_pairs.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/pointing_pairs.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/print_board.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/print_board.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/select_group.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/select_group.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/single_candidate.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/single_candidate.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/singles_chains.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/singles_chains.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/solver.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/solver.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/swordfish.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/swordfish.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/validation_check.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/validation_check.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/x_wing.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/x_wing.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/xyz_wing.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/xyz_wing.cpython-38.pyc -------------------------------------------------------------------------------- /codes/__pycache__/y_wing.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/__pycache__/y_wing.cpython-38.pyc -------------------------------------------------------------------------------- /codes/box_line.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:15:03 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | 13 | #%% BOX/LINE REDUCTION 14 | 15 | def box_line(board,cands,square_pos): 16 | ischanged = 0 17 | 18 | #check row by row 19 | for rows in range(9): 20 | use = cands.loc[rows].dropna() 21 | for val in range(1,10): 22 | inxs = use.apply(lambda x: val in x) 23 | # if len(inxs) == 0: 24 | if len(inxs) < 2: 25 | break 26 | inxs = list(inxs.index[inxs]) 27 | 28 | #find locations of the box/line reduction candidates 29 | if len(inxs) == 2 or len(inxs) == 3: 30 | board_pos = square_pos.loc[rows][inxs] 31 | if np.diff(board_pos).sum() == 0: 32 | square = board_pos.iloc[0] 33 | inx = pd.Series(square_pos[square_pos == square].stack().index.tolist()) 34 | 35 | #go through the square cells except the box/line 36 | for ix in inx: 37 | if ix[0] != rows: 38 | # if ix != (rows,board_pos.index[0]) and ix != (rows,board_pos.index[1]) and ix != (rows,board_pos.index[2]): 39 | try: 40 | temp = cands.iloc[ix].tolist() 41 | temp.remove(val) 42 | # cands.iloc[ix] = np.array(temp) 43 | cands.set_value(ix[0],ix[1],np.array(temp)) 44 | ischanged = 1 45 | print(f"R{ix[0]}C{ix[1]} Box/Line (row) reduction value {val} removed") 46 | # solver.solver(board,cands,square_pos) 47 | except: 48 | pass 49 | 50 | #columns 51 | for cols in range(9): 52 | use = cands[cols].dropna() 53 | for val in range(1,10): 54 | inxs = use.apply(lambda x: val in x) 55 | # if len(inxs) == 0: 56 | if len(inxs) < 2: 57 | break 58 | inxs = list(inxs.index[inxs]) 59 | 60 | #find locations of the box/line reduction candidates 61 | if len(inxs) == 2 or len(inxs) == 3: 62 | board_pos = square_pos[cols][inxs] 63 | if np.diff(board_pos).sum() == 0: 64 | square = board_pos.iloc[0] 65 | inx = pd.Series(square_pos[square_pos == square].stack().index.tolist()) 66 | 67 | #go through the square cells except the box/line 68 | for ix in inx: 69 | if ix[1] != cols: 70 | # if ix != (board_pos.index[0],cols) and ix != (board_pos.index[1],cols): 71 | try: 72 | temp = cands.iloc[ix].tolist() 73 | temp.remove(val) 74 | # cands.iloc[ix] = np.array(temp) 75 | cands.set_value(ix[0],ix[1],np.array(temp)) 76 | ischanged = 1 77 | print(f"R{ix[0]}C{ix[1]} Box/Line (col) reduction value {val} removed") 78 | # solver.solver(board,cands,square_pos) 79 | except: 80 | pass 81 | 82 | 83 | if ischanged: 84 | solver.solver(board,cands,square_pos) -------------------------------------------------------------------------------- /codes/candidate_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:27:33 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import pandas as pd 8 | import numpy as np 9 | from cells_seen import cells_seen 10 | 11 | #%% INITIALISE CANDIDATES 12 | def candidates(board): 13 | cands = pd.DataFrame(np.full((9,9),np.nan)) 14 | for row in range(9): 15 | for col in range(9): 16 | temp = [] 17 | if board.iloc[row,col] == ".": 18 | temp.extend(board.iloc[row,:]) 19 | temp.extend(board.iloc[:,col]) 20 | temp.extend(board.loc[board.index[row]][board.columns[col]].values.flatten()) 21 | temp = pd.Series(temp) 22 | drops = temp[temp!="."].unique() 23 | cand = pd.Series(np.arange(1,10),index=np.arange(1,10)).drop(drops).values.astype("O") 24 | 25 | #this part is for handling the "ValueError: setting an array element as a sequence" 26 | if len(cand) == 1: 27 | if cand%1 == 0.0: 28 | temp = cand.tolist() 29 | temp.append(99) 30 | cands[col][row] = np.array(temp).astype("O") 31 | temp.remove(99) 32 | cands[col][row] = np.array(temp).astype("O") 33 | else: 34 | cands[col][row] = cand 35 | 36 | return cands 37 | #%% UPDATE CANDIDATES 38 | def candidates_update(cands,row,col,val,square_pos): 39 | #remove the value from candidate matrix first 40 | cands.iloc[row,col] = np.nan 41 | 42 | #locate the cells seen by the key cell 43 | seen_cells = cells_seen((row,col),square_pos) 44 | 45 | #remove the value from the seen cells 46 | for i in seen_cells: 47 | try: 48 | if val in cands.iloc[i]: 49 | cands.loc[i[0]][i[1]] = np.delete(cands.iloc[i],np.where(cands.iloc[i]==val)) 50 | except: 51 | pass 52 | 53 | return cands -------------------------------------------------------------------------------- /codes/cells_seen.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:31:40 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import pandas as pd 8 | 9 | #finds returns all seen cells given the location of the key cell 10 | def cells_seen(inx,square_pos): 11 | cells = [] 12 | #box 13 | cells.extend(square_pos[square_pos == square_pos.iloc[inx]].stack().index) 14 | #rows 15 | cells.extend(square_pos.iloc[[inx[0]],:].stack().index) 16 | #cols 17 | cells.extend(square_pos.iloc[:,[inx[1]]].stack().index) 18 | cells = pd.Series(cells).unique() 19 | return cells -------------------------------------------------------------------------------- /codes/hidden_pairs_triples_quads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:10:49 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | from select_group import select_group 11 | # from hidden_remove import hidden_remove 12 | #interdependent modules, it has to be imported like below,otherwise it wont work 13 | import solver as solver 14 | import itertools 15 | #%% HIDDEN PAIRS-TRIPLES 16 | #find hidden pairs-triples-quads in the candidates dataframe 17 | def hidden_pairs_triples(board,cands,square_pos): 18 | ischanged = 0 19 | 20 | #go through combinations (2 and 3 elements at a time) 21 | for pair_triple_quad in [2,3]: 22 | #check for rows, columns and box consecutively 23 | for rowcolbox in ["row","col","box"]: 24 | #check each groups of rows, columns or boxes 25 | for group_no in range(9): 26 | #get the group 27 | use = select_group(rowcolbox,cands,group_no,square_pos) 28 | 29 | #find unique values in the group 30 | vals = [] 31 | for ix in use: 32 | vals.extend(ix) 33 | vals = pd.Series(vals).unique() 34 | 35 | #find pair, triple combinations of the unique values 36 | for comb in itertools.combinations(vals, pair_triple_quad): 37 | inxs = np.full(use.shape,False) 38 | for com in comb: 39 | inxs = (inxs) | (use.apply(lambda x: com in x)) 40 | 41 | #if it is a pair,triple 42 | if sum(inxs) == pair_triple_quad: 43 | no_of_hidden = sum(inxs) 44 | #determine hidden pair,triple indexes 45 | hidden_inxs = inxs.index[inxs] 46 | 47 | #remove the value from the specific cell of the candidates 48 | cands,ischanged = hidden_remove(rowcolbox,group_no,hidden_inxs,comb,cands,square_pos,no_of_hidden) 49 | 50 | if ischanged: 51 | solver.solver(board,cands,square_pos) 52 | 53 | #%% HIDDEN QUADS 54 | #find hidden quads in the candidates dataframe 55 | def hidden_quads(board,cands,square_pos): 56 | ischanged = 0 57 | 58 | #go through combinations (4 elements at a time) 59 | pair_triple_quad = 4 60 | #check for rows, columns and box consecutively 61 | for rowcolbox in ["row","col","box"]: 62 | #check each groups of rows, columns or boxes 63 | for group_no in range(9): 64 | #get the group 65 | use = select_group(rowcolbox,cands,group_no,square_pos) 66 | 67 | #find unique values in the group 68 | vals = [] 69 | for ix in use: 70 | vals.extend(ix) 71 | vals = pd.Series(vals).unique() 72 | 73 | #find quad combinations of the unique values 74 | for comb in itertools.combinations(vals, pair_triple_quad): 75 | inxs = np.full(use.shape,False) 76 | for com in comb: 77 | inxs = (inxs) | (use.apply(lambda x: com in x)) 78 | 79 | #if it is a quad 80 | if sum(inxs) == pair_triple_quad: 81 | no_of_hidden = sum(inxs) 82 | #determine hidden quad indexes 83 | hidden_inxs = inxs.index[inxs] 84 | 85 | #remove the value from the specific cell of the candidates 86 | cands,ischanged = hidden_remove(rowcolbox,group_no,hidden_inxs,comb,cands,square_pos,no_of_hidden) 87 | 88 | if ischanged: 89 | solver.solver(board,cands,square_pos) 90 | 91 | #removes values except hidden pairs, triples or quads from a group (row col or box) 92 | #it is different than "naked_remove" function 93 | def hidden_remove(rowcolbox,group_no,hidden_inxs,comb,cands,square_pos,no_of_hidden): 94 | pairtriplequad = {2:"Pair",3:"Triple",4:"Quad"} 95 | ischanged = 0 96 | for cells in hidden_inxs: 97 | if rowcolbox == "row": 98 | row = group_no 99 | col = cells 100 | elif rowcolbox == "col": 101 | row = cells 102 | col = group_no 103 | elif rowcolbox == "box": 104 | box_inx = square_pos[square_pos==group_no+1].stack().index 105 | row = box_inx[cells][0] 106 | col = box_inx[cells][1] 107 | 108 | #following line is for printing purposes only 109 | removed_vals = set(cands.iloc[row,col]).difference(set(comb)) 110 | 111 | #replace values in the cell with the intersection of combination and the cell 112 | if len(removed_vals): 113 | cands.iloc[row,col] = np.array(list(set(comb)&set(cands.iloc[row,col]))) 114 | print(f"R{row:<1}C{col:<1} Hidden {pairtriplequad[no_of_hidden]:>7}s ({rowcolbox:<3}), {str(removed_vals):<15} removed, {pairtriplequad[no_of_hidden]:>7}s: {str(comb):>6}") 115 | ischanged = 1 116 | 117 | return cands,ischanged -------------------------------------------------------------------------------- /codes/hidden_singles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:45:02 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import pandas as pd 8 | from candidate_handler import candidates_update 9 | #interdependent modules, it has to be imported like below,otherwise it wont work 10 | import solver as solver 11 | 12 | #%% HIDDEN SINGLES 13 | def hidden_singles(board,cands,square_pos): 14 | is_changed = 0 15 | #check rows 16 | for row in range(9): 17 | temp = [] 18 | for col in range(9): 19 | if board.iloc[row,col] == ".": 20 | temp.extend(cands.iloc[row,col]) 21 | valco = pd.Series(temp).value_counts() 22 | for valun in valco.index[valco == 1].values: #????? there can be 1 and only 1 single candidate in a row.. 23 | temp1 = cands.loc[row].dropna().apply(lambda x: valun in x) 24 | inx = temp1.index[temp1 == True] 25 | if len(inx): 26 | is_changed = 1 27 | for inxt in inx: 28 | print(f"R{row+1}C{inxt+1}={valun} : Hidden Singles (row)") 29 | board.iloc[row,inxt] = valun 30 | cands = candidates_update(cands,row,inxt,valun,square_pos) 31 | 32 | #check columns 33 | for col in range(9): 34 | temp = [] 35 | for row in range(9): 36 | if board.iloc[row,col] == ".": 37 | temp.extend(cands.iloc[row,col]) 38 | valco = pd.Series(temp).value_counts() 39 | for valun in valco.index[valco == 1].values: 40 | temp1 = cands.iloc[:,col].dropna().apply(lambda x: valun in x) 41 | inx = temp1.index[temp1 == True] 42 | if len(inx): 43 | is_changed = 1 44 | for inxt in inx: 45 | print(f"R{inxt+1}C{col+1}={valun} : Hidden Singles (col)") 46 | board.iloc[inxt,col] = valun 47 | cands = candidates_update(cands,inxt,col,valun,square_pos) 48 | 49 | #check squares 50 | for i in [[0,1,2],[3,4,5],[6,7,8]]: 51 | for j in [[0,1,2],[3,4,5],[6,7,8]]: 52 | a = cands.iloc[i,j] 53 | a_flat = pd.Series(a.values.flatten()).dropna() 54 | temp = [] 55 | for ix in a_flat: 56 | temp.extend(ix) 57 | valco = pd.Series(temp).value_counts() 58 | 59 | if any(valco == 1): 60 | to_change_all = valco.index[valco == 1].values 61 | for to_change in to_change_all: #loop all values to be changed (sometimes multiple changes needed in a single box) 62 | for rowx in a.index: 63 | for colx in a.columns: 64 | try: 65 | if board.iloc[rowx,colx] == ".": 66 | if to_change in a.loc[rowx][colx]: 67 | print(f"R{rowx+1}C{colx+1}={to_change} : Hidden Singles (square)") 68 | board.iloc[rowx,colx] = to_change 69 | cands = candidates_update(cands,rowx,colx,to_change,square_pos) 70 | is_changed = 1 71 | except: 72 | print("except") 73 | if is_changed: 74 | solver.solver(board,cands,square_pos) -------------------------------------------------------------------------------- /codes/init_board.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:29:00 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import sys 8 | import pandas as pd 9 | #%% construct the board from 81 digit grid string 10 | def init_board(grid): 11 | 12 | if len(grid)==81: 13 | board = [] 14 | 15 | for i in range(81): 16 | board.append(int(grid[i])) 17 | 18 | board = pd.Series(board).replace(0,".") 19 | board = pd.DataFrame(board.values.reshape((9,9))) 20 | 21 | board.index = ["A","A","A","B","B","B","C","C","C"] 22 | board.columns = ["A","A","A","B","B","B","C","C","C"] 23 | 24 | #helps finding the boxes when (row,col) pair is known 25 | square_pos = pd.DataFrame([ [1,1,1,2,2,2,3,3,3], 26 | [1,1,1,2,2,2,3,3,3], 27 | [1,1,1,2,2,2,3,3,3], 28 | [4,4,4,5,5,5,6,6,6], 29 | [4,4,4,5,5,5,6,6,6], 30 | [4,4,4,5,5,5,6,6,6], 31 | [7,7,7,8,8,8,9,9,9], 32 | [7,7,7,8,8,8,9,9,9], 33 | [7,7,7,8,8,8,9,9,9]]) 34 | return board,square_pos 35 | else: 36 | print(f"Board length: {len(grid)}") 37 | sys.exit(1) -------------------------------------------------------------------------------- /codes/naked_pairs_triples_quads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:10:49 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | from select_group import select_group 11 | # from naked_remove import naked_remove 12 | #interdependent modules, it has to be imported like below,otherwise it wont work 13 | import solver as solver 14 | import itertools 15 | #%% NAKED PAIRS-TRIPLES 16 | #find naked pairs-triples-quads in the candidates dataframe 17 | def naked_pairs_triples(board,cands,square_pos): 18 | ischanged = 0 19 | 20 | #go through combinations (2,3 and 4 elements at a time) 21 | for pair_triple_quad in [2,3]: 22 | #check for rows, columns and box consecutively 23 | for rowcolbox in ["row","col","box"]: 24 | #check each groups of rows, columns or boxes 25 | for group_no in range(9): 26 | #get the group 27 | use = select_group(rowcolbox,cands,group_no,square_pos) 28 | 29 | #find unique values in the group 30 | vals = [] 31 | for ix in use: 32 | vals.extend(ix) 33 | vals = pd.Series(vals).unique() 34 | 35 | #find pair, triple combinations of the unique values 36 | for comb in itertools.combinations(use, pair_triple_quad): 37 | comb = [i.tolist() for i in comb] 38 | temp = [] 39 | for i in comb: 40 | temp.extend(i) 41 | naked_values = pd.Series(temp).unique() 42 | 43 | if len(naked_values) == pair_triple_quad: 44 | # #determine naked pair,triple indexes 45 | naked_inxs = use.apply(lambda x: len(set(x).difference(naked_values))!=0) 46 | naked_inxs = naked_inxs.index[naked_inxs] 47 | # print(naked_inxs) 48 | # print("here") 49 | 50 | #if there are cells to remove naked pairs,triples 51 | if len(naked_inxs): 52 | # #removes pairs, triples values from the other cells for a group (row col or box) 53 | cands,ischanged = naked_remove(rowcolbox,group_no,naked_inxs,naked_values,cands,square_pos,pair_triple_quad) 54 | 55 | if ischanged: 56 | solver.solver(board,cands,square_pos) 57 | 58 | 59 | #%% NAKED QUADS 60 | #find naked quads in the candidates dataframe 61 | def naked_quads(board,cands,square_pos): 62 | ischanged = 0 63 | 64 | #go through combinations (4 elements at a time) 65 | pair_triple_quad = 4 66 | #check for rows, columns and box consecutively 67 | for rowcolbox in ["row","col","box"]: 68 | #check each groups of rows, columns or boxes 69 | for group_no in range(9): 70 | #get the group 71 | use = select_group(rowcolbox,cands,group_no,square_pos) 72 | 73 | #find unique values in the group 74 | vals = [] 75 | for ix in use: 76 | vals.extend(ix) 77 | vals = pd.Series(vals).unique() 78 | 79 | #find quad combinations of the unique values 80 | for comb in itertools.combinations(use, pair_triple_quad): 81 | comb = [i.tolist() for i in comb] 82 | temp = [] 83 | for i in comb: 84 | temp.extend(i) 85 | naked_values = pd.Series(temp).unique() 86 | 87 | if len(naked_values) == pair_triple_quad: 88 | # #determine naked quad indexes 89 | naked_inxs = use.apply(lambda x: len(set(x).difference(naked_values))!=0) 90 | naked_inxs = naked_inxs.index[naked_inxs] 91 | # print(naked_inxs) 92 | # print("here") 93 | 94 | #if there are cells to remove naked quads 95 | if len(naked_inxs): 96 | # #removes quads values from the other cells for a group (row col or box) 97 | cands,ischanged = naked_remove(rowcolbox,group_no,naked_inxs,naked_values,cands,square_pos,pair_triple_quad) 98 | 99 | if ischanged: 100 | solver.solver(board,cands,square_pos) 101 | 102 | #removes pairs, triples or quads values from the other cells for a group (row col or box) 103 | #it is different than "hidden_remove" function 104 | def naked_remove(rowcolbox,group_no,naked_inxs,comb,cands,square_pos,no_of_nakeds): 105 | pairtriplequad = {2:"Pair",3:"Triple",4:"Quad"} 106 | ischanged = 0 107 | for cells in naked_inxs: 108 | if rowcolbox == "row": 109 | row = group_no 110 | col = cells 111 | elif rowcolbox == "col": 112 | row = cells 113 | col = group_no 114 | elif rowcolbox == "box": 115 | box_inx = square_pos[square_pos==group_no+1].stack().index 116 | row = box_inx[cells][0] 117 | col = box_inx[cells][1] 118 | 119 | #following line is for printing purposes only 120 | removed_vals = set(comb)&set(cands.iloc[row,col]) 121 | 122 | #replace values in the cell with the intersection of combination and the cell 123 | if len(removed_vals): 124 | # cands.iloc[row,col] = np.array(list(set(comb)&set(cands.iloc[row,col]))) 125 | # cands.iloc[row,col] = np.array(list(set(cands.iloc[row,col]).difference(set(comb)))) 126 | # cands.loc[row][col] = np.array(list(set(cands.iloc[row,col]).difference(set(comb)))) 127 | # cands.set_value(row,col,np.array(list(set(cands.iloc[row,col]).difference(set(comb))))) 128 | cands.at[row,col] = np.array(list(set(cands.iloc[row,col]).difference(set(comb)))) 129 | print(f"R{row:<1}C{col:<1} Naked {pairtriplequad[no_of_nakeds]:>7}s ({rowcolbox:<3}), {str(removed_vals):<15} removed, {pairtriplequad[no_of_nakeds]:>7}s: {str(comb):>6}") 130 | ischanged = 1 131 | 132 | return cands,ischanged -------------------------------------------------------------------------------- /codes/naked_quads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:10:49 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | from select_group import select_group 11 | from naked_remove import naked_remove 12 | #interdependent modules, it has to be imported like below,otherwise it wont work 13 | import solver as solver 14 | import itertools 15 | #%% NAKED QUADS 16 | #find naked quads in the candidates dataframe 17 | def naked_pairs_triples_quads(board,cands,square_pos): 18 | ischanged = 0 19 | 20 | #go through combinations (4 elements at a time) 21 | pair_triple_quad = 4 22 | #check for rows, columns and box consecutively 23 | for rowcolbox in ["row","col","box"]: 24 | #check each groups of rows, columns or boxes 25 | for group_no in range(9): 26 | #get the group 27 | use = select_group(rowcolbox,cands,group_no,square_pos) 28 | 29 | #find unique values in the group 30 | vals = [] 31 | for ix in use: 32 | vals.extend(ix) 33 | vals = pd.Series(vals).unique() 34 | 35 | #find quad combinations of the unique values 36 | for comb in itertools.combinations(use, pair_triple_quad): 37 | comb = [i.tolist() for i in comb] 38 | temp = [] 39 | for i in comb: 40 | temp.extend(i) 41 | naked_values = pd.Series(temp).unique() 42 | 43 | if len(naked_values) == pair_triple_quad: 44 | # #determine naked quad indexes 45 | naked_inxs = use.apply(lambda x: len(set(x).difference(naked_values))!=0) 46 | naked_inxs = naked_inxs.index[naked_inxs] 47 | # print(naked_inxs) 48 | # print("here") 49 | 50 | #if there are cells to remove naked quads 51 | if len(naked_inxs): 52 | # #removes quads values from the other cells for a group (row col or box) 53 | cands,ischanged = naked_remove(rowcolbox,group_no,naked_inxs,naked_values,cands,square_pos,pair_triple_quad) 54 | 55 | if ischanged: 56 | solver.solver(board,cands,square_pos) 57 | -------------------------------------------------------------------------------- /codes/not used/for_swordfish.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/not used/for_swordfish.sav -------------------------------------------------------------------------------- /codes/not used/for_xyz_wing.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/not used/for_xyz_wing.sav -------------------------------------------------------------------------------- /codes/not used/for_xyz_wing2.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/codes/not used/for_xyz_wing2.sav -------------------------------------------------------------------------------- /codes/not used/hidden_remove.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Jan 11 21:26:17 2020 4 | 5 | @author: omerzulal 6 | """ 7 | import numpy as np 8 | 9 | #removes values except hidden pairs, triples or quads from a group (row col or box) 10 | #it is different than "naked_remove" function 11 | def hidden_remove(rowcolbox,group_no,hidden_inxs,comb,cands,square_pos,no_of_hidden): 12 | pairtriplequad = {2:"Pair",3:"Triple",4:"Quad"} 13 | ischanged = 0 14 | for cells in hidden_inxs: 15 | if rowcolbox == "row": 16 | row = group_no 17 | col = cells 18 | elif rowcolbox == "col": 19 | row = cells 20 | col = group_no 21 | elif rowcolbox == "box": 22 | box_inx = square_pos[square_pos==group_no+1].stack().index 23 | row = box_inx[cells][0] 24 | col = box_inx[cells][1] 25 | 26 | #following line is for printing purposes only 27 | removed_vals = set(cands.iloc[row,col]).difference(set(comb)) 28 | 29 | #replace values in the cell with the intersection of combination and the cell 30 | if len(removed_vals): 31 | cands.iloc[row,col] = np.array(list(set(comb)&set(cands.iloc[row,col]))) 32 | print(f"R{row:<1}C{col:<1} Hidden {pairtriplequad[no_of_hidden]:>7}s ({rowcolbox:<3}), {str(removed_vals):<15} removed, {pairtriplequad[no_of_hidden]:>7}s: {str(comb):>6}") 33 | ischanged = 1 34 | 35 | return cands,ischanged -------------------------------------------------------------------------------- /codes/not used/hidden_triples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:10:49 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | from select_group import select_group 11 | from hidden_remove import hidden_remove 12 | #interdependent modules, it has to be imported like below,otherwise it wont work 13 | import solver as solver 14 | import itertools 15 | #%% HIDDEN TRIPLES 16 | #find hidden triples in the candidates dataframe 17 | def hidden_triples(board,cands,square_pos): 18 | ischanged = 0 19 | 20 | #for all 21 | for rowcolbox in ["row","col","box"]: 22 | for group_no in range(9): 23 | use = select_group(rowcolbox,cands,group_no,square_pos) 24 | 25 | vals = [] 26 | for ix in use: 27 | vals.extend(ix) 28 | vals = pd.Series(vals).unique() 29 | #go through combinations (3 elements at a time) 30 | for comb in itertools.combinations(vals, 3): 31 | inxs = (use.apply(lambda x: comb[0] in x)) | (use.apply(lambda x: comb[1] in x)) | (use.apply(lambda x: comb[2] in x)) 32 | 33 | #if it is a triple 34 | if sum(inxs) == 3: 35 | no_of_hidden = sum(inxs) 36 | #determine hidden triple indexes 37 | triple_inx = inxs.index[inxs] 38 | 39 | #remove the value from the specific cell of the candidates 40 | cands = hidden_remove(rowcolbox,group_no,triple_inx,comb,cands,square_pos,no_of_hidden) 41 | 42 | if ischanged: 43 | solver.solver(board,cands,square_pos) 44 | -------------------------------------------------------------------------------- /codes/not used/naked_pairs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:02:01 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | #interdependent modules, it has to be imported like below,otherwise it wont work 10 | import solver as solver 11 | 12 | #%% NAKED PAIRS 13 | def naked_pairs(board,cands,square_pos): 14 | ischanged = 0 15 | 16 | #check rows 17 | for row in range(9): 18 | use = cands.loc[row].dropna() 19 | try: 20 | pairs = use[use.index[use.apply(lambda x: len(x) == 2)]] 21 | if len(pairs) >= 2: 22 | for i in pairs.index: 23 | for j in pairs.index[pairs.index > i]: 24 | if all(pairs[i] == pairs[j]): 25 | # print(f"Row{row} Naked Pair: {pairs[i]}") 26 | #remove found pair values in the rest of the cells in candidates 27 | inxs = use.index.tolist() 28 | inxs.remove(i) 29 | inxs.remove(j) 30 | for k in inxs: 31 | temp = cands.loc[row][k].tolist() 32 | for l in pairs[i]: 33 | try: 34 | temp.remove(l) 35 | print(f"R{row}C{k} Naked Pair (row) {pairs[i]}, {l} removed") 36 | ischanged = 1 37 | except: 38 | pass 39 | # print("No length item") 40 | cands.loc[row][k] = np.array(temp) 41 | except: 42 | pass 43 | # print(f"No pair found in row") 44 | 45 | #check columns 46 | for col in range(9): 47 | use = cands[col].dropna() 48 | try: 49 | pairs = use[use.index[use.apply(lambda x: len(x) == 2)]] 50 | if len(pairs) >= 2: 51 | for i in pairs.index: 52 | for j in pairs.index[pairs.index > i]: 53 | if all(pairs[i] == pairs[j]): 54 | #remove found pair values in the rest of the cells in candidates 55 | inxs = use.index.tolist() 56 | inxs.remove(i) 57 | inxs.remove(j) 58 | for k in inxs: 59 | temp = cands[col][k].tolist() 60 | for l in pairs[i]: 61 | try: 62 | temp.remove(l) 63 | print(f"R{k}C{col} Naked Pair (col) {pairs[i]}, {l} removed") 64 | ischanged = 1 65 | except: 66 | pass 67 | 68 | cands[col][k] = np.array(temp) 69 | 70 | except: 71 | pass 72 | 73 | #check squares 74 | for i in [[0,1,2],[3,4,5],[6,7,8]]: 75 | for j in [[0,1,2],[3,4,5],[6,7,8]]: 76 | use = cands.iloc[i,j] 77 | use_flat = pd.Series(use.values.flatten()).dropna() 78 | try: 79 | pairs = use_flat[use_flat.index[use_flat.apply(lambda x: len(x) == 2)]] 80 | if len(pairs) >= 2: 81 | for pair1 in pairs.index: 82 | for pair2 in pairs.index[pairs.index > pair1]: 83 | if all(pairs[pair1] == pairs[pair2]): 84 | # print(f"Square Naked Pair: {pairs[pair1]}") 85 | #remove found pair values in the rest of the cells in candidates 86 | 87 | for ii in i: 88 | for jj in j: 89 | try: 90 | if len(use.loc[ii][jj])>=2: 91 | check = use.loc[ii][jj] == pairs[pair1] 92 | try: 93 | check = all(check) 94 | except: 95 | pass 96 | if not check: 97 | temp = use.loc[ii][jj].tolist() 98 | for l in pairs[pair1]: 99 | try: 100 | temp.remove(l) 101 | print(f"R{ii}C{jj} Naked Pair (square) {pairs[pair1]}, {l} removed") 102 | ischanged = 1 103 | except: 104 | pass 105 | use.loc[ii][jj] = np.array(temp) 106 | 107 | except: 108 | pass 109 | cands.iloc[i,j] = use 110 | except: 111 | pass 112 | 113 | 114 | 115 | if ischanged: 116 | solver.solver(board,cands,square_pos) 117 | -------------------------------------------------------------------------------- /codes/not used/naked_remove.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Jan 12 00:37:37 2020 4 | 5 | @author: omerzulal 6 | """ 7 | 8 | import numpy as np 9 | 10 | #removes pairs, triples or quads values from the other cells for a group (row col or box) 11 | #it is different than "hidden_remove" function 12 | def naked_remove(rowcolbox,group_no,naked_inxs,comb,cands,square_pos,no_of_nakeds): 13 | pairtriplequad = {2:"Pair",3:"Triple",4:"Quad"} 14 | ischanged = 0 15 | for cells in naked_inxs: 16 | if rowcolbox == "row": 17 | row = group_no 18 | col = cells 19 | elif rowcolbox == "col": 20 | row = cells 21 | col = group_no 22 | elif rowcolbox == "box": 23 | box_inx = square_pos[square_pos==group_no+1].stack().index 24 | row = box_inx[cells][0] 25 | col = box_inx[cells][1] 26 | 27 | #following line is for printing purposes only 28 | removed_vals = set(comb)&set(cands.iloc[row,col]) 29 | 30 | #replace values in the cell with the intersection of combination and the cell 31 | if len(removed_vals): 32 | # cands.iloc[row,col] = np.array(list(set(comb)&set(cands.iloc[row,col]))) 33 | cands.iloc[row,col] = np.array(list(set(cands.iloc[row,col]).difference(set(comb)))) 34 | print(f"R{row:<1}C{col:<1} Naked {pairtriplequad[no_of_nakeds]:>7}s ({rowcolbox:<3}), {str(removed_vals):<15} removed, {pairtriplequad[no_of_nakeds]:>7}s: {str(comb):>6}") 35 | ischanged = 1 36 | 37 | return cands,ischanged -------------------------------------------------------------------------------- /codes/not used/naked_triples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:07:16 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | #interdependent modules, it has to be imported like below,otherwise it wont work 10 | import solver as solver 11 | #%% NAKED TRIPLES (squares missing) 12 | import itertools 13 | 14 | def naked_triples(board,cands,square_pos): 15 | ischanged = 0 16 | 17 | #rows 18 | for row in range(9): 19 | use = cands.loc[row].dropna() 20 | 21 | #go through combinations (3 elements at a time) 22 | for comb in itertools.combinations(use, 3): 23 | comb = [i.tolist() for i in comb] 24 | temp = [] 25 | for i in comb: 26 | temp.extend(i) 27 | naked_triple = pd.Series(temp).unique() 28 | if len(naked_triple) == 3: 29 | 30 | #remove naked triple elements from the rest of the row 31 | for j in use.index: 32 | try: 33 | used = use[j].tolist() 34 | if not used in comb: 35 | temp1 = used 36 | for nt in naked_triple: 37 | try: 38 | temp1.remove(nt) 39 | use[j] = np.array(temp1) 40 | print(f"R{row}C{j} Naked Triple (rows), {nt} removed, Triple: {naked_triple}") 41 | ischanged = 1 42 | except: 43 | pass 44 | except: 45 | pass 46 | 47 | cands.loc[row] = use 48 | 49 | #cols 50 | for col in range(9): 51 | use = cands[col].dropna() 52 | 53 | #go through combinations (3 elements at a time) 54 | for comb in itertools.combinations(use, 3): 55 | comb = [i.tolist() for i in comb] 56 | temp = [] 57 | for i in comb: 58 | temp.extend(i) 59 | naked_triple = pd.Series(temp).unique() 60 | if len(naked_triple) == 3: 61 | 62 | #remove naked triple elements from the rest of the column 63 | for j in use.index: 64 | try: 65 | used = use[j].tolist() 66 | if not used in comb: 67 | temp1 = used 68 | for nt in naked_triple: 69 | try: 70 | temp1.remove(nt) 71 | use[j] = np.array(temp1) 72 | print(f"R{j}C{col} Naked Triple (columns), {nt} removed, Triple: {naked_triple}") 73 | # print(f"Column{col} Naked triples removed {nt}, {comb}") 74 | ischanged = 1 75 | except: 76 | pass 77 | except: 78 | pass 79 | 80 | cands[col] = use 81 | 82 | if ischanged: 83 | solver.solver(board,cands,square_pos) -------------------------------------------------------------------------------- /codes/pointing_pairs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:12:25 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | 13 | #%% POINTING PAIRS 14 | def pointing_pairs(board,cands,square_pos): 15 | # print("Pointing Pairs") 16 | ischanged = 0 17 | 18 | for i in [[0,1,2],[3,4,5],[6,7,8]]: 19 | for j in [[0,1,2],[3,4,5],[6,7,8]]: 20 | use = cands.iloc[i,j] 21 | use_flat = pd.Series(use.values.flatten()).dropna() 22 | 23 | temp = [] 24 | for ix in use_flat: 25 | temp.extend(ix) 26 | 27 | valco = pd.Series(temp).value_counts() 28 | # try: 29 | pair_vals = valco.index[(valco == 2) | (valco == 3)] 30 | 31 | for pair_val in pair_vals: 32 | pointrows, pointcols = [],[] 33 | for ii in use.index: 34 | for jj in use.columns: 35 | try: 36 | if pair_val in use.loc[ii][jj].tolist(): 37 | pointrows.extend([ii]) 38 | pointcols.extend([jj]) 39 | except: 40 | pass 41 | 42 | #pairs point in the column direction 43 | try: 44 | if not any(np.diff(pointcols)): 45 | change_col = cands[pointcols[0]].dropna().drop(pointrows) 46 | for rows in change_col.index: 47 | temp = change_col[rows].tolist() 48 | try: 49 | temp.remove(pair_val) 50 | # cands.iloc[rows,pointcols[0]] = np.array(temp) 51 | cands.set_value(rows,pointcols[0],np.array(temp)) 52 | print(f"R{rows}C{pointcols[0]} Pointing Pairs (cols), {pair_val} removed") 53 | ischanged = 1 54 | # solver.solver(board,cands,square_pos) 55 | except: 56 | pass 57 | except: 58 | pass 59 | 60 | #pairs point in the row direction 61 | try: 62 | if not any(np.diff(pointrows)): 63 | change_col = cands.loc[pointrows[0]].dropna().drop(pointcols) 64 | for cols in change_col.index: 65 | temp = change_col[cols].tolist() 66 | try: 67 | temp.remove(pair_val) 68 | # cands.iloc[pointrows[0],cols] = np.array(temp) 69 | cands.set_value(pointrows[0],cols,np.array(temp)) 70 | print(f"R{pointrows[0]}C{cols} Pointing Pairs (rows), {pair_val} removed") 71 | ischanged = 1 72 | # solver.solver(board,cands,square_pos) 73 | except: 74 | pass 75 | except: 76 | pass 77 | 78 | if ischanged: 79 | solver.solver(board,cands,square_pos) 80 | -------------------------------------------------------------------------------- /codes/print_board.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:13:56 2020 4 | 5 | @author: omer.eker 6 | """ 7 | 8 | #prints the sudoku board 9 | def print_board(board): 10 | boardprint = board.copy() 11 | boardprint.columns = range(1,10) 12 | boardprint.index = ["A","B","C","D","E","F","G","H","I"] 13 | # sns.heatmap(board,annot=True,linecolor="k") 14 | print(boardprint) -------------------------------------------------------------------------------- /codes/select_group.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sat Jan 11 21:24:46 2020 4 | 5 | @author: omerzulal 6 | """ 7 | import pandas as pd 8 | 9 | #selects the group (row col or box) 10 | def select_group(rowcolbox,cands,group_no,square_pos): 11 | if rowcolbox == "row": 12 | use = cands.loc[group_no].dropna() 13 | elif rowcolbox == "col": 14 | use = cands[group_no].dropna() 15 | elif rowcolbox == "box": 16 | inx = square_pos[square_pos==group_no+1].stack().index 17 | use = pd.Series([cands.iloc[i] for i in inx]).dropna() 18 | return use -------------------------------------------------------------------------------- /codes/single_candidate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:37:53 2020 4 | 5 | @author: omer.eker 6 | """ 7 | 8 | from candidate_handler import candidates_update 9 | #interdependent modules, it has to be imported like below,otherwise it wont work 10 | import solver as solver 11 | 12 | #%% SINGLE CANDIDATES 13 | def single_cand(board,cands,square_pos): 14 | ischanged = 0 15 | for row in range(9): 16 | for col in range(9): 17 | if board.iloc[row,col] == ".": 18 | cand = cands.iloc[row,col] 19 | # cand = int(cands.iloc[row,col][0]) 20 | try: 21 | lenx = len(cand) 22 | if lenx == 1: 23 | ischanged = 1 24 | print(f"R{row+1}C{col+1}={cand[0]} : Single Candidate") 25 | board.iloc[row,col] = cand[0] 26 | cands = candidates_update(cands,row,col,cand[0],square_pos) 27 | except: 28 | ischanged = 1 29 | print(f"R{row+1}C{col+1}={cand} : Single Candidate (except)") 30 | board.iloc[row,col] = cand 31 | cands = candidates_update(cands,row,col,cand,square_pos) 32 | if ischanged: 33 | solver.solver(board,cands,square_pos) 34 | -------------------------------------------------------------------------------- /codes/singles_chains.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:22:15 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | from cells_seen import cells_seen 13 | import itertools 14 | 15 | #%% Simple Colouring (Singles Chains) 16 | # finds and returns conjugate pairs (strong links) 17 | def conjugate_pairs(cands,board): 18 | # find strong links for each value 19 | strlinx_rows = [] 20 | strlinx_cols = [] 21 | strlinx_boxs = [] 22 | for val in range(1,10): 23 | 24 | #go through all rows 25 | strlinx_row = [] 26 | for row in range(9): 27 | use = cands.loc[row].dropna() 28 | temp = [] 29 | for i in use: 30 | temp.extend(i) 31 | 32 | valco = pd.Series(temp).value_counts() 33 | try: 34 | if valco.loc[val] == 2: 35 | inx_temp = use.apply(lambda x: val in x) 36 | inx = inx_temp.index[inx_temp == True] 37 | # print(f"value: {val} row: {row} strong link columns: {inx}") 38 | strlinx_row.append([(row,inx[0]),(row,inx[1])]) 39 | 40 | except: 41 | pass 42 | strlinx_rows.append(strlinx_row) 43 | 44 | #go through all cols 45 | strlinx_col = [] 46 | for col in range(9): 47 | use = cands[col].dropna() 48 | temp = [] 49 | for i in use: 50 | temp.extend(i) 51 | 52 | valco = pd.Series(temp).value_counts() 53 | try: 54 | if valco.loc[val] == 2: 55 | inx_temp = use.apply(lambda x: val in x) 56 | inx = inx_temp.index[inx_temp == True] 57 | # print(f"value: {val} col: {col} strong link indexes: {inx}") 58 | strlinx_col.append([(inx[0],col),(inx[1],col)]) 59 | 60 | except: 61 | pass 62 | strlinx_cols.append(strlinx_col) 63 | 64 | #go through all boxes 65 | strlinx_box = [] 66 | for i in [[0,1,2],[3,4,5],[6,7,8]]: 67 | for j in [[0,1,2],[3,4,5],[6,7,8]]: 68 | use_box = cands.iloc[i,j] 69 | use = pd.Series(use_box.values.flatten()).dropna() 70 | 71 | temp = [] 72 | for ix in use: 73 | temp.extend(ix) 74 | 75 | valco = pd.Series(temp).value_counts() 76 | try: 77 | if valco.loc[val] == 2: 78 | tempinx = [] 79 | for ir in use_box.index: 80 | for ic in use_box.columns: 81 | if board.iloc[ir,ic] == ".": 82 | if val in use_box.loc[ir][ic]: 83 | # print(f"value: {val} strong link indexes: R{ir}C{ic}") 84 | tempinx.append((ir,ic)) 85 | strlinx_box.append(tempinx) 86 | except: 87 | pass 88 | strlinx_boxs.append(strlinx_box) 89 | 90 | return strlinx_rows,strlinx_cols,strlinx_boxs 91 | 92 | #auxiliary function used by the recursive function 93 | def colour_cell(boxrowcol,i,search,match_matrix,val,sl_uniq,strlinx_rows,strlinx_cols,strlinx_boxs): 94 | i = pd.Series(i) 95 | inx = i.index[i == search] 96 | 97 | try: 98 | pair = i[abs(inx-1)].tolist()[0] 99 | if len(pair) and pd.isnull(match_matrix.iloc[pair]): 100 | # print(f"{boxrowcol}: Connection from {search} to {pair} for {val+1}") 101 | 102 | #colour conjugate pairs with the same group but different colour 103 | group = match_matrix.iloc[search][1] 104 | colour = colours[abs(colours.index[match_matrix.iloc[search][0] == colours][0]-1)]+str(group) 105 | match_matrix.iloc[pair] = colour 106 | 107 | match_matrix = simple_colouring(val,pair,strlinx_rows,strlinx_cols,strlinx_boxs,sl_uniq,match_matrix) 108 | except: 109 | pass 110 | return match_matrix 111 | 112 | #the recursive function searches for the cell 113 | def simple_colouring(val,search,strlinx_rows,strlinx_cols,strlinx_boxs,sl_uniq,match_matrix): 114 | #first start searching it in the boxes 115 | for i in strlinx_boxs[val]: 116 | match_matrix = colour_cell("box",i,search,match_matrix,val,sl_uniq,strlinx_rows,strlinx_cols,strlinx_boxs) 117 | 118 | #then rows 119 | for i in strlinx_rows[val]: 120 | match_matrix = colour_cell("row",i,search,match_matrix,val,sl_uniq,strlinx_rows,strlinx_cols,strlinx_boxs) 121 | 122 | #then cols 123 | for i in strlinx_cols[val]: 124 | match_matrix = colour_cell("col",i,search,match_matrix,val,sl_uniq,strlinx_rows,strlinx_cols,strlinx_boxs) 125 | 126 | return match_matrix 127 | 128 | def singles_chains_eliminate(sl_uniq,match_matrix,rem,cands,square_pos): 129 | ischanged = 0 130 | for rows in cands.index: 131 | for cols in cands.columns: 132 | use = cands.iloc[rows,cols] 133 | 134 | try: 135 | chk = not pd.isnull(use).all() 136 | except: 137 | chk = False 138 | 139 | #locate candidate cells for elimination 140 | if chk: 141 | if rem+1 in use: 142 | # if pd.isnull(match_matrix.iloc[rows,cols]) and rem+1 in use: 143 | #locate all seen cells 144 | seen_cells = cells_seen((rows,cols),square_pos) 145 | #find intersection between seen cells and the conjugate pairs 146 | inters = list(set(seen_cells) & set(sl_uniq)) 147 | 148 | #remove the cell of interest from the intersection cells, if any 149 | try: 150 | inters.remove((rows,cols)) 151 | except: 152 | pass 153 | 154 | #if these cells intersect 155 | if len(inters): 156 | #obtain colours of the seen conjugate paris 157 | inter_colours = pd.Series([match_matrix.iloc[i] for i in inters]).unique() 158 | # print(f"val:{rem+1}, cell:{(rows,cols)}, intersection cells: {inters}, intersection colours: {inter_colours}") 159 | 160 | if len(inter_colours) == 2: 161 | if inter_colours[0][1] == inter_colours[1][1] and \ 162 | inter_colours[0][0] != inter_colours[1][0]: 163 | temp = use.tolist() 164 | try: 165 | temp.remove(rem+1) 166 | # cands.iloc[rows,cols] = np.array(temp) 167 | cands.set_value(rows,cols,np.array(temp)) 168 | print(f"R{rows}C{cols} Singles-Chains, removed {rem+1}") 169 | ischanged = 1 170 | except: 171 | pass 172 | elif len(inter_colours) >= 3: 173 | # print("Inter Len >= 3") 174 | for combs in itertools.combinations(inter_colours,2): 175 | # print(combs) 176 | if combs[0][1] == combs[1][1] and combs[0][0] != combs[1][0]: 177 | temp = use.tolist() 178 | try: 179 | temp.remove(rem+1) 180 | # cands.iloc[rows,cols] = np.array(temp) 181 | cands.set_value(rows,cols,np.array(temp)) 182 | print(f"R{rows}C{cols} Singles-Chains, removed {rem+1}") 183 | ischanged = 1 184 | except: 185 | pass 186 | 187 | return ischanged,cands 188 | 189 | #call single chain functions 190 | def singles_chains(board,cands,square_pos): 191 | ischanged = 0 192 | 193 | #select the value before finding conjugate pairs 194 | for val in range(9): 195 | #find unique strong links for a value 196 | strlinx_rows,strlinx_cols,strlinx_boxs=conjugate_pairs(cands,board) 197 | 198 | sl = strlinx_rows[val].copy() 199 | sl.extend(strlinx_cols[val]) 200 | sl.extend(strlinx_boxs[val]) 201 | temp = [] 202 | for i in sl: 203 | temp.extend(i) 204 | sl_uniq = pd.Series(temp).unique() 205 | 206 | #construct empty matrix for conjugate pairs 207 | match_matrix = pd.DataFrame(np.full((9,9),np.nan)) 208 | 209 | #start cell 210 | group = 0 211 | for init_cell in sl_uniq: 212 | # print(f"Start Cell: {init_cell}") 213 | 214 | if pd.isnull(match_matrix.iloc[init_cell]): 215 | match_matrix.iloc[init_cell] = colours[0]+str(group) 216 | group+=1 217 | 218 | #call recursive function 219 | match_matrix = simple_colouring(val,init_cell,strlinx_rows,strlinx_cols,strlinx_boxs,sl_uniq,match_matrix) 220 | 221 | #call single chains elimination function 222 | ischanged,cands = singles_chains_eliminate(sl_uniq,match_matrix,val,cands,square_pos) 223 | if ischanged: 224 | solver.solver(board,cands,square_pos) 225 | 226 | #colours for the simple colouring strategy 227 | colours = pd.Series(["B","R"]) 228 | 229 | 230 | -------------------------------------------------------------------------------- /codes/solver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:40:39 2020 4 | 5 | @author: omer.eker 6 | """ 7 | 8 | from single_candidate import single_cand 9 | from hidden_singles import hidden_singles 10 | from hidden_pairs_triples_quads import hidden_pairs_triples, hidden_quads 11 | from naked_pairs_triples_quads import naked_pairs_triples, naked_quads 12 | from pointing_pairs import pointing_pairs 13 | from box_line import box_line 14 | from x_wing import x_wing 15 | from y_wing import y_wing 16 | from singles_chains import singles_chains 17 | from xyz_wing import xyz_wing 18 | from swordfish import swordfish 19 | 20 | 21 | import sys 22 | 23 | #%% SOLVER FUNCTION 24 | def solver(board,cands,square_pos): 25 | #run strategies in listed order as long as the board has empty cells (".") 26 | if (board==".").any().any(): 27 | single_cand(board,cands,square_pos) 28 | hidden_singles(board,cands,square_pos) 29 | naked_pairs_triples(board,cands,square_pos) 30 | hidden_pairs_triples(board,cands,square_pos) 31 | pointing_pairs(board,cands,square_pos) 32 | box_line(board,cands,square_pos) 33 | naked_quads(board,cands,square_pos) 34 | hidden_quads(board,cands,square_pos) 35 | x_wing(board,cands,square_pos) 36 | y_wing(board,cands,square_pos) 37 | singles_chains(board,cands,square_pos) 38 | xyz_wing(board,cands,square_pos) 39 | swordfish(board,cands,square_pos) 40 | else: 41 | print("COMPLETE!!!!!") 42 | # break 43 | sys.exit(0) -------------------------------------------------------------------------------- /codes/sudoku_solver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | SUDOKU SOLVER V19 4 | -swordfish added 5 | @author: omerzulal 6 | """ 7 | 8 | import pandas as pd 9 | import numpy as np 10 | import warnings 11 | warnings.filterwarnings("ignore") 12 | import time 13 | 14 | from print_board import print_board 15 | from validation_check import check_valid 16 | from candidate_handler import candidates, candidates_update 17 | from cells_seen import cells_seen 18 | from solver import solver 19 | from init_board import init_board 20 | 21 | #%% Alternative grids from the internet 22 | 23 | ################################### 24 | #GRIDS FROM ANDREW STUART'S WEBSITE 25 | ################################### 26 | #(SOLVED) 27 | # grid = "000004028406000005100030600000301000087000140000709000002010003900000507670400000" 28 | #(SOLVED) moderate in Andrew Stuart website, naked triple 29 | # grid = "720096003000205000080004020000000060106503807040000000030800090000702000200430018" 30 | # (SOLVED) board for naked pairs, X-wing, Y-wing 31 | # grid = "309000400200709000087000000750060230600904008028050041000000590000106007006000104" 32 | # (SOLVED) board for hidden pairs 33 | # grid = "000000000904607000076804100309701080008000300050308702007502610000403208000000000" 34 | # board for pointing pairs, X-wing, simple colouring, XYZ wing, ***NEEDS*** X-cycles 35 | # grid = "000704005020010070000080002090006250600070008053200010400090000030060090200407000" 36 | # (SOLVED) board for X-wing example, y-wing, simple colouring 37 | # grid = "093004560060003140004608309981345000347286951652070483406002890000400010029800034" 38 | # (SOLVED) board for hidden triples 39 | # grid="300000000970010000600583000200000900500621003008000005000435002000090056000000001" 40 | # (SOLVED) board for hidden triples 41 | # grid="000000000231090000065003100008924000100050006000136700009300570000010843000000000" 42 | # (SOLVED) board for pointing pairs, box/line reduction, simple coloring 43 | # grid="000921003009000060000000500080403006007000800500700040003000000020000700800195000" 44 | # (SOLVED) board for pointing pairs, Y-wing (multiply y-wings at different locations) 45 | # grid = "900240000050690231020050090090700320002935607070002900069020073510079062207086009" 46 | # (SOLVED) pointing pairs, simple coloring 47 | # grid = "007083600039706800826419753640190387080367000073048060390870026764900138208630970" 48 | # simple coloring, pointing pairs, ***NEEDS*** x-cycles 49 | # grid = "200041006400602010016090004300129640142060590069504001584216379920408165601900482" 50 | # (SOLVED) simple coloring 51 | # grid = "062900000004308000709000400600801000003000200000207003001000904000709300000004120" 52 | # (SOLVED) simple coloring 53 | # grid = "000000070000090810500203004800020000045000720000000003400308006072010000030000000" 54 | # (SOLVED) simple coloring 55 | # grid = "090200350012003000300008000000017000630000089000930000000700002000300190078009030" 56 | # (SOLVED) simple coloring 57 | # grid = "400800003006010409000005000010060092000301000640050080000600000907080100800009004" 58 | # XYZ-wing, ***NEEDS*** aligned pair exclusion, X-cycle, XY-chain 59 | # grid = "092001750500200008000030200075004960200060075069700030008090020700003089903800040" 60 | # XYZ-wing, ***NEEDS*** WXYZ wing 61 | # grid = "600000008500908007820001030340209080200080300180307025750400092900005004400090003" 62 | # (SOLVED) XYZ-wing 63 | # grid = "072000680000700000500016000000028100200371006004560000000130004000007000015000890" 64 | # (SOLVED) XYZ-wing 65 | # grid = "000100000000000980705062310109007403000000000807200105091740802053000000000001000" 66 | # (SOLVED) XYZ-wing 67 | # grid = "008207090000000420400010056000980000006000100000023000760090004082000000040801200" 68 | # (SOLVED) XYZ-wing 69 | # grid = "900850000050201000600030008005070012080000070730010500100020003000109020000043006" 70 | # (SOLVED) Swordfish 71 | # grid = "008009000300057001000100009230000070005406100060000038900003000700840003000700600" 72 | # (SOLVED) Swordfish 73 | # grid = "980010020002700000000009010700040800600107002009030005040900000000005700070020039" 74 | # (SOLVED) Swordfish 75 | # grid = "108000067000050000000000030006100040450000900000093000200040010003002700807001005" 76 | # (SOLVED) Swordfish 77 | # grid = "107300040800006000050870630090000510000000007700060080000904000080100002410000000" 78 | # (SOLVED) Swordfish 79 | # grid = "300040000000007048000000907010003080400050020050008070500300000000000090609025300" 80 | 81 | ################################### 82 | #GRIDS FROM GITHUB AARON FREDERICK 83 | ################################### 84 | # # https://github.com/sok63/sudoku-1/blob/master/sample/p096_sudoku.txt 85 | # Grid 01 Simple colouring, pointing pairs ***NEEDS*** x-cycles 86 | # grid = "200000006000602000010090004300009600040000090009500001500010370000408000600000002" 87 | # Grid 02 ***NEEDS*** 3D medusa 88 | # grid = "000050000300284000010000408086003200400000009005700140507000030000637001000020000" 89 | # (SOLVED) Grid 03 90 | # grid = "000000907000420180000705026100904000050000040000507009920108000034059000507000000" 91 | # (SOLVED) Grid 05 92 | # grid = "020810740700003100090002805009040087400208003160030200302700060005600008076051090" 93 | # (SOLVED) Grid 06 94 | # grid = "840000000000000000000905001200380040000000005000000000300000820009501000000700000" 95 | # (SOLVED) Grid 07, Y Wing 96 | # grid = "007000400060070030090203000005047609000000000908130200000705080070020090001000500" 97 | # (SOLVED) Grid 26 98 | # grid = "500400060009000800640020000000001008208000501700500000000090084003000600060003002" 99 | # (SOLVED) Grid 48 pointing pairs, box/line reduction 100 | # grid = "001007090590080001030000080000005800050060020004100000080000030100020079020700400" 101 | 102 | ################################### 103 | #GRIDS FROM ANDREW CRACKING THE CRYPTIC 104 | ################################### 105 | # (SOLVED)cracking the cryptic LGFL83r67M X-wing, hidden quads 106 | # grid = "000000010210003480039800200060304900000000000001607040008002170026700098090000000" 107 | # (SOLVED) cracking the cryptic mgp6LprDM8, pointing pairs, X-wing, simple colouring, swordfish 108 | # grid = "800000100010790560007108040570020400008010795103050080701003006000000010002001900" 109 | # (SOLVED) cracking the cryptic 3gLbRPQ42d pointing pairs, box/line reduction, X-wing, naked quads 110 | # grid = "000000700000001080300020004090002060005000800080700050200070003060500000003000000" 111 | #cracking the cryptic JDFGD8p2m3 box/line reduction, simple colouring, ***NEEDS*** diabolic strategies 112 | # grid = "800000300040001000200470000400000000010002070003090005000685000008000120000009003" 113 | # (SOLVED) cracking the cryptic RRf6bgb9GG hidden triples 114 | # grid = "609102080000000400502000000000020304100005000020000506000801000000000009805907040" 115 | # (SOLVED)cracking the cryptic BHBjPFJJP4 swordfish 116 | # grid = "040300600001002090000000000000000000030600900007001020060400300700000008002007010" 117 | #cracking the cryptic jjPp4LNbBH pointing pairs, box/line reduction, simple colouring, ***NEEDS*** x-cycles 118 | # grid = "000005302006030900080009070000100800070000095001060200054000000002000000810000006" 119 | # (SOLVED) cracking the cryptic 7PD6nQ77hT hidden triple (box), pointing pairs, X-wing, 120 | # grid = "008390000010000000700012300500900040300000005040008006005670001000000060000021400" 121 | # (SOLVED) cracking the cryptic 497rhdJp27 swordfish 122 | # grid = "004700003030060090900001800800002500020070080001400007009500001050010030200006700" 123 | # cracking the cryptic jHFDQq6BtT ***NEEDS*** Gurth's Theorem (Diagonal Symmetric) 124 | # grid = "000001002003000040050060700000800070007003800900050001006080200040600007200009060" 125 | 126 | ################################### 127 | #GRIDS FROM MY SUDOKU BOOKS 128 | ################################### 129 | # (SOLVED) maxi sudoku book (zor 115) pointing pairs 130 | # grid = "000904002080070600037000100500340000079000540000095007003000980001050020200809000" 131 | # (SOLVED) maxi sudoku book (zor 129) 132 | # grid = "600103089000000000800005100000008905058406730402700000003500004000000000570904001" 133 | # (SOLVED) maxi sudoku book (zor 147) pointing pairs 134 | # grid = "070100000000065481000090200036000004700010005500000730005070000987540000000008060" 135 | # maxi sudoku book (cok zor 70) simple colouring, ***NEEDS*** X-cycles, 136 | # grid = "000980100900001780000002060301007040002000600040200908080700000023100007005096000" 137 | # maxi sudoku book (cok zor 73) ***NEEDS*** XYZ-Wing, X-Cycles, XY-Chain 138 | # grid = "302040000000600903000700010006400302053000790409002500020007000507004000000010405" 139 | # maxi sudoku book (cok zor 93) ***NEEDS*** XYZ-Wing, XY-Chain, WXYZ Wing, 140 | # grid = "000910500000800067804050000582000040009000800060000259000020301320006000008073000" 141 | # maxi sudoku book (cok zor 123) ***NEEDS*** X-cycles, WXYZ wing, Altern Inference Chains, XY-Chain, 142 | # grid = "003740860008500100600080007090000000800604001000000020500010004002009700049036200" 143 | # maxi sudoku book (cok zor 127) ***NEEDS*** Hidden Unique Rectangles, Aligned Pair Exclusion, Altern Inference Chains, Y-wing, 3D Medusa,XY-Chain 144 | # grid = "100005000500000603023106000780010000000932000000080042000203970305000006000700005" 145 | # (SOLVED) maxi sudoku book (cok zor 130) pointing pairs, simple colouring 146 | # grid = "720000000030020019109804000280000300000090000003000027000309102490060070000000065" 147 | # (SOLVED) maxi sudoku book (cok zor 131) Y-wing 148 | # grid = "000809000075000000082400031056080000109000504000090210710002890000000120000901000" 149 | # maxi sudoku book (cok zor 135) pointing pairs, ***NEEDS*** X-cycles 150 | # grid = "020700100008050003001020060800145006000000000600298005040010900500070600007004050" 151 | # (SOLVED) maxi sudoku book (cok zor 142) pointing pairs, simple colouring 152 | # grid = "190580700007001050500090000010020007009000600600030080000040003070100900006073045" 153 | # maxi sudoku book (cok zor 150) pointing pairs, XYZ wing, ***NEEDS*** XY chain, WXYZ wing 154 | # grid = "410005000200401008800000103008000070070106050020000800302000005100904007000300086" 155 | # (SOLVED) sudoku book 3 zor puzzle 17 156 | # grid = "007650000040007230080000005090806004200000001700301050600000010019200080000069400" 157 | # (SOLVED) sudoku book 3 zor puzzle 18 158 | # grid = "100978004090000080008000100900204008500000001700506002007000800060000040800652009" 159 | # (SOLVED) sudoku book 3 cok zor puzzle 2 pointing pairs 160 | # grid = "400000005060000010002306900008050200000704000007030500003902400020000080600000001" 161 | # (SOLVED) sudoku book 3 imkansiz puzzle 2 pointing pairs, X-wing 162 | # grid = "009004003030070090800900600001008007040010020600400500005002008080030070400700100" 163 | 164 | #use following to convert board tables to single line string: 165 | # grid = np.array2string(board.replace(".",0).values.flatten()).translate({ord(i): None for i in "[]\n "}) 166 | 167 | #%% user interface for the console 168 | # uinput = input("Enter the board in 81 digit format where empty cells are filled with zeros: ") 169 | # if len(uinput) == 81: 170 | # grid = uinput 171 | # elif len(uinput) == 0: 172 | # pass 173 | # else: 174 | # print(f"Incorrect Sudoku Board (Entered {len(uinput)} digits only!)") 175 | 176 | 177 | #%% RUN THE SOLVER 178 | t1 = time.time() 179 | board,square_pos = init_board(grid) 180 | print_board(board) 181 | 182 | #check validity before solving it 183 | if check_valid(board): 184 | cands = candidates(board) 185 | solver(board,cands,square_pos) 186 | else: 187 | print("The board is not valid!") 188 | 189 | # #check validity after solving it 190 | if check_valid(board): 191 | print(f"Solving took {round(time.time()-t1,2)} seconds") 192 | board.index = np.arange(1,10) 193 | board.columns = np.arange(1,10) 194 | print_board(board) 195 | print(f"{(board == '.').sum().sum()} Missing Elements Left in The Board!") 196 | 197 | 198 | -------------------------------------------------------------------------------- /codes/swordfish.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:16:23 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | import itertools 13 | 14 | #%%fast starter, load data 15 | # import pickle 16 | # # pickle.dump([cands,board,grid,square_pos],open("for_swordfish.sav","wb")) 17 | # cands,board,grid,square_pos = pickle.load(open("for_swordfish.sav","rb")) 18 | 19 | #%% SWORDFISH 20 | def swordfish(board,cands,square_pos): 21 | ischanged = 0 22 | #check swordfish for both rows and columns 23 | for rowcol in ["rows","cols"]: 24 | # print(rowcol) 25 | if rowcol == "cols": 26 | cands = cands.T 27 | ischanged = 0 28 | 29 | #construct candidate table 30 | wings = [] 31 | for i in range(1,10): 32 | wing = [] 33 | for rows in cands.index: 34 | use = cands.loc[rows].dropna() 35 | temp = [] 36 | for cols in use.index: 37 | if i in use[cols]: 38 | temp.append(cols) 39 | wing.append(temp) 40 | wings.append(wing) 41 | 42 | for wing in range(len(wings)): 43 | # print(wings[wing]) 44 | for comb in itertools.combinations(enumerate(wings[wing]),3): 45 | # print(comb) 46 | flag = False 47 | temp = [] 48 | for i in comb: 49 | if len(i[1])<=1: 50 | flag = True 51 | temp.extend(i[1]) 52 | 53 | if flag: 54 | continue 55 | temp = pd.Series(temp) 56 | valco = temp.value_counts() 57 | if len(valco)==3 and sum(valco)>=6: 58 | # print(comb) 59 | # print(valco) 60 | cols_to_remove = temp.unique() 61 | # print(cols_to_remove) 62 | 63 | #remove values from the columns 64 | rem = wing+1 65 | for cols in cols_to_remove: 66 | #drop the wing rows and nans 67 | use = cands[cols].dropna() 68 | for rows in use.index: 69 | if rem in use[rows] and not rows in [comb[0][0],comb[1][0],comb[2][0]]: 70 | cands.set_value(rows,cols,np.delete(cands.iloc[rows,cols],np.where(cands.iloc[rows,cols]==rem))) 71 | ischanged = 1 72 | print(f"R{rows}C{cols} Swordfish, {rem} removed from {rowcol}") 73 | if ischanged: 74 | solver.solver(board,cands,square_pos) 75 | 76 | cands = cands.T 77 | return cands 78 | 79 | # cands = swordfish(board,cands,square_pos) -------------------------------------------------------------------------------- /codes/validation_check.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 09:20:58 2020 4 | 5 | @author: omer.eker 6 | """ 7 | 8 | import pandas as pd 9 | 10 | def row_check(board,isprint=False): 11 | check = True 12 | for row in range(9): 13 | a = board.iloc[row,:].value_counts() 14 | try: 15 | a = a.drop(".") 16 | except: 17 | pass 18 | 19 | check = check and not any(a>1) 20 | 21 | if isprint: 22 | if check: 23 | print("Row Check: Passed") 24 | else: 25 | print("Row Check: Failed") 26 | 27 | return check 28 | 29 | def col_check(board,isprint=False): 30 | check = True 31 | for col in range(9): 32 | a = board.iloc[:,col].value_counts() 33 | try: 34 | a = a.drop(".") 35 | except: 36 | pass 37 | 38 | check = check and not any(a>1) 39 | 40 | if isprint: 41 | if check: 42 | print("Column Check: Passed") 43 | else: 44 | print("Column Check: Failed") 45 | 46 | return check 47 | 48 | def square_check(board,isprint=False): 49 | check = True 50 | for i in [[0,1,2],[3,4,5],[6,7,8]]: 51 | for j in [[0,1,2],[3,4,5],[6,7,8]]: 52 | a = pd.Series(board.iloc[i,j].values.flatten()).value_counts() 53 | try: 54 | a = a.drop(".") 55 | except: 56 | pass 57 | 58 | check = check and not any(a>1) 59 | 60 | if isprint: 61 | if check: 62 | print("Square Check: Passed") 63 | else: 64 | print("Square Check: Failed") 65 | 66 | return check 67 | 68 | #validation check function 69 | def check_valid(board): 70 | rcheck = row_check(board) 71 | ccheck = col_check(board) 72 | cucheck = square_check(board) 73 | 74 | return rcheck and ccheck and cucheck -------------------------------------------------------------------------------- /codes/x_wing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:16:23 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | 13 | #%% X-WING 14 | def x_wing(board,cands,square_pos): 15 | #check xwings for both rows and columns 16 | for rowcol in ["rows","cols"]: 17 | if rowcol == "cols": 18 | cands = cands.T 19 | ischanged = 0 20 | #construct candidate table 21 | wings = [] 22 | for i in range(1,10): 23 | wing = [] 24 | for rows in cands.index: 25 | use = cands.loc[rows].dropna() 26 | temp = [] 27 | for cols in use.index: 28 | if i in use[cols]: 29 | temp.append(cols) 30 | wing.append(temp) 31 | wings.append(wing) 32 | 33 | #loop through the candidates and eliminate 34 | for wing in range(len(wings)): 35 | # print(wings[wing]) 36 | for i in range(9): 37 | for j in range(i+1,9): 38 | if (len(wings[wing][i]) == 2) & (len(wings[wing][j]) == 2): 39 | # print(f"pair_r{i}c{j}") 40 | if wings[wing][i] == wings[wing][j]: 41 | #remove candidates in the columns 42 | for rem in wings[wing][i]: 43 | use = cands[rem].dropna() 44 | for ix in use.index: 45 | if not((ix == i) | (ix == j)): 46 | temp = use[ix].tolist() 47 | try: 48 | temp.remove(wing+1) 49 | print(f"R{ix}C{rem} X-Wing, removed {wing+1} from {rowcol}") 50 | ischanged = 1 51 | except: 52 | pass 53 | use[ix] = np.array(temp) 54 | cands[rem] = use 55 | cands = cands.T 56 | if ischanged: 57 | solver.solver(board,cands,square_pos) -------------------------------------------------------------------------------- /codes/xyz_wing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:18:16 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | from cells_seen import cells_seen 11 | #interdependent modules, it has to be imported like below,otherwise it wont work 12 | import solver as solver 13 | import itertools 14 | 15 | #%%fast starter, load data 16 | # import pickle 17 | # # pickle.dump([cands,board,grid,square_pos],open("for_xyz_wing2.sav","wb")) 18 | # cands,board,grid,square_pos = pickle.load(open("for_xyz_wing2.sav","rb")) 19 | 20 | #%% XYZ-WING 21 | #eliminate the candidates from intersections 22 | def xyz_wing_cand_eliminate(key,comb,cands,rem,square_pos,board): 23 | comb = list(comb) 24 | comb.remove(key) 25 | ischanged = 0 26 | 27 | #determine the wing cells 28 | for i in comb: 29 | if square_pos.iloc[i]==square_pos.iloc[key]: 30 | wing_key = i #the wing in the same box with the hinge 31 | comb.remove(i) 32 | #the other wing 33 | wing2 = comb[0] 34 | wing_key_box = square_pos[square_pos == square_pos.iloc[wing_key]].stack().index 35 | 36 | seen_cells_wing_key = set(wing_key_box).intersection(cells_seen(wing2,square_pos)) 37 | 38 | try: 39 | seen_cells_wing_key.remove(key) 40 | seen_cells_wing_key.remove(wing_key) 41 | except: 42 | pass 43 | 44 | for inx in seen_cells_wing_key: 45 | if board.iloc[inx] == ".": 46 | temp = cands.iloc[inx].tolist() 47 | try: 48 | temp.remove(rem) 49 | # cands.iloc[inx] = np.array(temp) 50 | cands.set_value(inx[0],inx[1],np.array(temp)) 51 | print(f"R{inx[0]}C{inx[1]} XYZ-Wing, removed {rem}") 52 | ischanged = 1 53 | except: 54 | pass 55 | return ischanged,cands 56 | 57 | def xyz_wing(board,cands,square_pos): 58 | ischanged = 0 59 | #determine the number of candidates for each cell (if else statement is necessary otherwise str.len() gives error when all column or row values are Nan) 60 | lenx = cands.apply(lambda x: x.str.len() if not x.isnull().all() else x) 61 | #find the location of bivalue and trivalue cells 62 | inxtwos = lenx[(lenx == 2)|(lenx == 3)].stack().index 63 | 64 | #go through triple-combinations of bivalue cells 65 | for comb in itertools.combinations(inxtwos, 3): 66 | lencounts = pd.Series([lenx.iloc[i] for i in comb]).value_counts() 67 | # print(comb) 68 | 69 | try: 70 | lencheck = lencounts[2] == 2 and lencounts[3] == 1 71 | except: 72 | lencheck = False 73 | 74 | if lencheck: 75 | temp = [] 76 | for co in comb: 77 | temp.extend(cands.iloc[co]) 78 | valco = pd.Series(temp).value_counts() 79 | 80 | #check if the combination construct a AB,BC,ABC formation 81 | if len(valco) == 3 and all(valco.sort_values().values == [2,2,3]): 82 | 83 | #find the hinge cell 84 | for i in comb: 85 | if lenx.iloc[i] == 3: 86 | key = i 87 | 88 | #make sure that the hinge cell (with 3 values) sees the other 2 and in the same box with one of them 89 | seen_cells = cells_seen(key,square_pos) 90 | inters = set(seen_cells).intersection(comb) 91 | 92 | if len(inters)==3 and key: 93 | key_box = square_pos[square_pos == square_pos.iloc[key]].stack().index 94 | if len(set(key_box).intersection(comb)) == 2: 95 | #determine the value to be removed from candidates 96 | rem = valco.index[valco==3][0] 97 | 98 | #remove value from wing boxes 99 | ischanged,cands = xyz_wing_cand_eliminate(key,comb,cands,rem,square_pos,board) 100 | if ischanged: 101 | solver.solver(board,cands,square_pos) 102 | 103 | 104 | # xyz_wing(board,cands,square_pos) 105 | -------------------------------------------------------------------------------- /codes/y_wing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 10 10:18:16 2020 4 | 5 | @author: omer.eker 6 | """ 7 | import numpy as np 8 | import pandas as pd 9 | from candidate_handler import candidates_update 10 | #interdependent modules, it has to be imported like below,otherwise it wont work 11 | import solver as solver 12 | import itertools 13 | 14 | #%% Y-WING 15 | #find if there is a key cell in the "rectangular formation y-wing" 16 | def find_rect_key(comb): 17 | #check if comb[0] is the key 18 | if (comb[0][0] == comb[1][0] and comb[0][1] == comb[2][1]) or (comb[0][0] == comb[2][0] and comb[0][1] == comb[1][1]): 19 | key = comb[0] 20 | #check if comb[1] is the key 21 | elif (comb[1][0] == comb[0][0] and comb[1][1] == comb[2][1]) or (comb[1][0] == comb[2][0] and comb[1][1] == comb[0][1]): 22 | key = comb[1] 23 | #check if comb[2] is the key 24 | elif (comb[2][0] == comb[0][0] and comb[2][1] == comb[1][1]) or (comb[2][0] == comb[1][0] and comb[2][1] == comb[0][1]): 25 | key = comb[2] 26 | else: 27 | key = False 28 | 29 | return key 30 | 31 | #find if two cells are in the same box 32 | def is_same_box(a,b,square_pos): 33 | box = False 34 | if square_pos.iloc[a[0],a[1]] == square_pos.iloc[b[0],b[1]]: 35 | # box = square_pos.iloc[a[0],a[1]] 36 | box = True 37 | return box 38 | 39 | #find if there is a key cell in "not rectangular formation y-wing" 40 | def find_key(comb,square_pos): 41 | #check if comb[0] is the key 42 | if (is_same_box(comb[0], comb[1],square_pos) and (comb[0][0]==comb[2][0] or comb[0][1]==comb[2][1])) or\ 43 | (is_same_box(comb[0], comb[2],square_pos) and (comb[0][0]==comb[1][0] or comb[0][1]==comb[1][1])): 44 | key = comb[0] 45 | #check if comb[1] is the key 46 | elif (is_same_box(comb[1], comb[0],square_pos) and (comb[1][0]==comb[2][0] or comb[1][1]==comb[2][1])) or\ 47 | (is_same_box(comb[1], comb[2],square_pos) and (comb[1][0]==comb[0][0] or comb[1][1]==comb[0][1])): 48 | key = comb[1] 49 | #check if comb[2] is the key 50 | elif (is_same_box(comb[2], comb[0],square_pos) and (comb[2][0]==comb[1][0] or comb[2][1]==comb[1][1])) or\ 51 | (is_same_box(comb[2], comb[1],square_pos) and (comb[2][0]==comb[0][0] or comb[2][1]==comb[0][1])): 52 | key = comb[2] 53 | else: 54 | key = False 55 | 56 | return key 57 | 58 | def y_wing_cand_eliminate(key,comb,cands,rem,isrect,square_pos,board): 59 | comb = list(comb) 60 | comb.remove(key) 61 | ischanged = 0 62 | 63 | if isrect: 64 | for inx in [(comb[0][0],comb[1][1]),(comb[1][0],comb[0][1])]: 65 | if inx != key and board.iloc[inx] == ".": 66 | temp = cands.iloc[inx].tolist() 67 | try: 68 | temp.remove(rem) 69 | cands.iloc[inx] = np.array(temp) 70 | print(f"R{inx[0]}C{inx[1]} Y-Wing, removed {rem}") 71 | ischanged = 1 72 | except: 73 | pass 74 | else: 75 | #loop box indexes one by one 76 | for wing in comb: 77 | wing2 = comb[0] if comb[0]!=wing else comb[1] 78 | inx_wing = list(square_pos[square_pos == square_pos.iloc[wing]].stack().index) 79 | inx_wing.remove(wing) 80 | try: 81 | inx_wing.remove(key) 82 | except: 83 | pass 84 | 85 | for inx in inx_wing: 86 | if board.iloc[inx] == "." and (inx[0]==wing2[0] or inx[1]==wing2[1]): 87 | temp = cands.iloc[inx].tolist() 88 | try: 89 | temp.remove(rem) 90 | cands.iloc[inx] = np.array(temp) 91 | print(f"R{inx[0]}C{inx[1]} Y-Wing, removed {rem}") 92 | ischanged = 1 93 | except: 94 | pass 95 | return ischanged 96 | 97 | def y_wing(board,cands,square_pos): 98 | ischanged = 0 99 | #determine the number of candidates for each cell (if else statement is necessary otherwise str.len() gives error when all column or row values are Nan) 100 | lenx = cands.apply(lambda x: x.str.len() if not x.isnull().all() else x) 101 | #find the location of bivalue cells 102 | inxtwos = lenx[lenx == 2].stack().index 103 | 104 | #go through triple-combinations of bivalue cells 105 | for comb in itertools.combinations(inxtwos, 3): 106 | temp = [] 107 | for co in comb: 108 | temp.extend(cands.iloc[co]) 109 | valco = pd.Series(temp).value_counts() 110 | 111 | #check if the combination construct a AB,BC,AC formation 112 | if len(valco) == 3: 113 | if len(valco.unique()) == 1: 114 | #make sure that only 2 of the 3 cells are in the same column or row 115 | row = pd.Series(list(map(lambda x: x[0],comb))) 116 | col = pd.Series(list(map(lambda x: x[1],comb))) 117 | if 2 in row.value_counts().values or \ 118 | 2 in col.value_counts().values: 119 | # make sure that not all of them in the same box 120 | box = list(map(lambda x: square_pos.iloc[x],comb)) 121 | if np.diff(box).any(): 122 | 123 | #eliminate the ones which all corners are in different boxes and does not form a rectangular formation 124 | rect_key = find_rect_key(comb) 125 | if not (len(pd.Series(box).unique()) == 3 and rect_key == False): 126 | key = find_key(comb,square_pos) 127 | 128 | if rect_key: 129 | for ix in list(valco.index): 130 | if not ix in cands.iloc[rect_key]: 131 | rem = ix 132 | # print(f"values = {list(valco.index)}, inx = {comb}, rect_key_cell = {rect_key}, rem = {rem} Rectangular") 133 | #remove value from wing boxes 134 | ischanged = y_wing_cand_eliminate(rect_key,comb,cands,rem,1,square_pos,board) 135 | 136 | if key: 137 | #determine the value to be removed from candidates 138 | for ix in list(valco.index): 139 | if not ix in cands.iloc[key]: 140 | rem = ix 141 | 142 | #determine the key cell 143 | # print(f"values = {list(valco.index)}, inx = {comb}, key_cell = {key}, rem = {rem}") 144 | 145 | #remove value from wing boxes 146 | ischanged = y_wing_cand_eliminate(key,comb,cands,rem,0,square_pos,board) 147 | if ischanged: 148 | solver.solver(board,cands,square_pos) 149 | -------------------------------------------------------------------------------- /images/board_final.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/images/board_final.JPG -------------------------------------------------------------------------------- /images/board_initial.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/images/board_initial.JPG -------------------------------------------------------------------------------- /images/board_input string.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/images/board_input string.JPG -------------------------------------------------------------------------------- /images/candidates.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerfarukeker/Sudoku-Solver/8ec1c7dad67bfcffe51847cc76e6e8ec642187c8/images/candidates.JPG --------------------------------------------------------------------------------