DSA_GROUP PROJECT_REPORT GR 3
DSA_GROUP PROJECT_REPORT GR 3
ALGORITHM
GROUP PROJECT
SUDOKU SOLVER
GROUP 3:
Agrani Anirudh Mishra MB17GBA174
Deepak Kannan MB17GBA190
Jitendra Ramshakal Varma MB17GBA200
Mahesh Kumar Yadav MB17GBA204
Raunak Kumar Gupta MB17GBA217
Vikas Gupta MB17GBA238
Executive Summary:
Sudoku Solver
Problem Statement
Create a GUI interface and create a Python program that takes in an incomplete Sudoku grid and
returns the same grid with all the completed values. The solution will use some search algorithms to
solve a puzzle, and return the puzzle solution, as follows:
Description
Sudoku is a partially completed 9x9 grid puzzle. The task is to fill the grid with digits such that
each row, each column, and each 3x3 block contains digits from 1 to 9 exactly once. We focus
on the Sudoku puzzle because it is both constraint satisfaction problems (CSP) and requires the knowledge
of the common data structures learned in class like sets, Numpy matrix, Forward Checking and
Backtracking algorithm etc.
The scope of this project is to minimize time to solve 9x9 grid Sudoku puzzle by using different
search algorithms and heuristics.
Approach
1. For the first model, we cast the Sudoku puzzle as a constraint satisfaction
problem .
The details are as followed:
Figure 1: Illustration of CSP constraints: blue and green lines represents binary constraint for tile
A1 while red represents unary constraints for the input tiles.
The variables are each cell on the grid, so there are 81 variables: Xij = the cell value at row
i and column j, A ≤ i ≤ I; 1 ≤ j ≤ 9.
• The domain of each cell is 1, 2, 3, ..., 9.
• The constraints are:
1. Unary Constraint: m constraints, f(Xij) : xij = initially assigned value, where m is
number of initially assigned cells
2. Binary Constraint: 810 constraints, f(Xij; Yij): return x ! = y, for
– Digits in the same row have to be distinct (8 constraints per tile)
– Digits in the same column have to be distinct (8 constraints per tile)
– Digits in the same block have to be distinct (4 more constraints per tile)
There are 20 constraints per tile. Therefore, there are 20 * 81 / 2 = 810 constraints in
total where factor of 1/2 accounts for double counting.
With this setup, we will use backtracking search with following heuristics to solve for solution.
• Most Constrained Variable (MCV): to choose an unassigned cell, pick the cell that has the
fewest consistent values left in the domain
2. Forward Checking and Finding minimum remaining values, the available values to be filled in a
cell were stored in a set. The row and column values were stored in Python list. And the sudoku
matrix was initialized using Numpy matrix.
3. The output grid was designed using PyQt5 UI designer library for python. It also helped us to
debug the code and check for any anomaly in intermediate solution steps.
But there are three important point to be highlighted while filling a value in EMPTY cell. We have to look for
missing value in first 3*3 grid. Than we have to look for the missing values in each row and their respective
columns and then fill the no. without repeating them in Grid, Rows and Columns.
The first and most naïve solver starts at the top‐left square and moves from left‐to‐right, top‐to bottom, filling
each blank square with a number 1 to 9 until the grid is invalid (that is, a number is duplicated in a row, column
or 3×3 region) or until the grid is filled and valid (that is, solved). If the grid is invalid, the solver will backtrack
until it is valid and continue forward again.
Each square in a grid has a domain of up to 9 values (1 to 9) that is reduced according to numbers already
present in the intersecting row, column and region.
In this grid, we can reduce the domain of the top‐left square to {1,4} since 2 and 3 already appear in the first
column. Likewise, we can restrict the domain of the top‐right square to {2}, since 3 and 1 occur in the rightmost
column and 4 appears in the top‐right region.
This solver acts in the same way as the previous implementation except that it restricts the domains of the grid
before it starts, only using numbers present
in the initial domains. While the domain
restriction requires computation, it should result
in significantly less backtracking to find the
solution of a grid.
2. Backtracking:
In backtracking algorithms, you try to build a solution one step at a time. If at some step it becomes clear that
the current path that you are on cannot lead to a solution you go back to the previous step (backtrack) and
choose a different path. Briefly, once you exhaust all your options at a certain step you go back. Backtracking is
also known as depth-first search.
A backtracking algorithm visits the empty cells in some order, filling in digits sequentially, or backtracking
when the number is found to be not valid. Briefly, a program would solve a puzzle by placing the digit "1" in the
first cell and checking if it is allowed to be there. If there are no violations (checking row, column, and box
constraints) then the algorithm advances to the next cell, and places a "1" in that cell. When checking for
violations, if it is discovered that the "1" is not allowed, the value is advanced to "2". If a cell is discovered
where none of the 9 digits is allowed, then the algorithm leaves that cell blank and moves back to the previous
cell. The value in that cell is then incremented by one. This is repeated until the allowed value in the last (81st)
cell is discovered.
The disadvantage of this method is that the solving time may be comparatively slow compared to algorithms
modelled after deductive methods.
1. Like all other Backtracking problems, we can solve Sudoku by one by one assigning numbers to empty
cells.
2. Before assigning a number, we need to confirm that the same number is not present in current row, current
column and current 3X3 sub grid.
3. If number is not present in respective row, column or sub grid, we can assign the number, and recursively
check if this assignment leads to a solution or not. If the assignment doesn’t lead to a solution, then we try
next number for current empty cell. And if none of number (1 to 9) lead to solution, we return false.
3. Forward Checking
The first improvement on backtracking search is forward checking. Backtracking search had to place a value and
then check for conflicts. Instead it is easier to just maintain a list of which possible values each square can
possibly have given the other numbers that have been assigned. Then when the values are being assigned to that
square, only consider the ones that do not directly conflict with the other already placed numbers.
For a size three puzzle forward checks can be stored in a nine by nine by nine boolean array. Basically, each
square has its own array of nine boolean values corresponding to each of the numbers that could possibly go in
that square. If the third value in the array is set to false, then that square cannot contain a three. Maintaining these
lists is simple. Whenever a new value x is assigned is assigned, go to every other square in the same row, column
and box and mark false in its array for value x.
The basic idea with forward checking is to make a backtracking depth-first search in the solution space, but
every time a variable is assigned, the domains of all the affected variables (due to constraints) will be modified.
If a domain becomes totally empty, we backtrack. It all sounds terribly complicated but it's really a
straightforward approach, assigning the variables one at a time and whenever something is assigned revise the
possible values for the rest of the values.
Another method for improving the backtracking search is the minimum remaining values
heuristic. The minimum remaining values heuristic is used to alter the order in which squares are guessed
in order to reduce the number of branches at each level.
Basically instead of choosing the first empty square, the square with the least number of possible
values is chosen. For example, in the puzzle given below one of the two shaded squares would be
chosen next. This is because the two shaded squares have two possible values and the other squares
have three possible values.
By choosing the square with only two possible values instead of three the search tree only
branches in two directions instead of three. Basically, the search tree is reduced in size by a factor of two thirds.
The backtracking algorithm will be implemented using the minimum remaining value (MRV) heuristic. The
order of values to be attempted for each variable can be arbitrary. When a variable is assigned, forward
checking will be applied to further reduce variables domains.
In our code, above mentioned logic is implemented by storing the eligible digits for a cell in the sets and a list is
prepared from these sets. So, in short, we have a list of sets from which we find the cell with minimum number
of eligible digits. This is done by calculating the set with minimum number of values in it. Then the Sudoku
problem is started with this set. Once the smallest set is exhausted we again start searching for the next smallest
set and the loop is repeated till the time all the available sets in the list are exhausted.
The above approaches were implemented using the code given in annexure 1.
Output:
Limitations:
1). No input validation:
Input validation such as unique values, character, zero does not exist. Validation for
characters and only natural numbers till 9 should be added.
2). Works on only 9*9 matrix
A functionality can be added which will calculate the time complexity of the solution from the given
input and the number of unfilled cells. This way, it can also estimate the time required for the solution.
Annexure 1: Python Code.
import numpy as np
import sys
import time
class sudoku_gui(Ui_MainWindow):
def __init__(self,app):
self.valid_values = np.empty([9,9], dtype = set) #Matrix Set for storing valid values
self.app = app
#disable_input: Disable Textbox in GUI so that user cannot input any value
def disable_input(self):
layout = self.GridLayout.itemAtPosition(row,col)
text_box = layout.widget()
text_box.setReadOnly(True)
def read_input(self):
text_box = layout.widget()
text = text_box.text()
if text == "":
continue
else:
self.sudoku[row][col]=int(text)
def fixed_empty_cell(self):
value = self.sudoku[row][col]
if value == 0:
self.empty_cell.append((row,col))
else:
self.fixed_cell.append((row,col))
def change_label_style(self):
row,col = empty
layout = self.GridLayout.itemAtPosition(row,col)
text_box = layout.widget()
old_style = text_box.styleSheet()
text_box.setStyleSheet(new_style)
self.app.processEvents()
def set_solved_value(self,row,col):
layout = self.GridLayout.itemAtPosition(row,col)
text_box = layout.widget()
text_box.setText(str(self.sudoku[row][col]))
self.app.processEvents()
def del_cell_value(self,row,col):
layout = self.GridLayout.itemAtPosition(row,col)
text_box = layout.widget()
text_box.setText("")
self.app.processEvents()
def get_grid_cells(self,row,col):
grid = [(0,1,2),(3,4,5),(6,7,8)]
grid_cells = []
for i in range(0,3):
item = grid[i]
if row in item:
grid_row = item
if col in item:
grid_col = item
grid_cells.append((row,col))
return grid_cells
def remove_invalid_row_values(self,row,valid):
valid.discard(self.sudoku[row][col])
def remove_invalid_col_values(self,col,valid):
valid.discard(self.sudoku[row][col])
#Removes the values present in a given row/col grid from "Valid" set
def remove_invalid_grid_values(self,row,col,valid):
grid_cells = self.get_grid_cells(row,col)
valid.discard(self.sudoku[row][col])
#Find Values for each empty cell and stores it into a Numpy matrix of type set
def get_valid_value(self):
valid = set(range(1,10))
self.remove_invalid_row_values(row,valid)
self.remove_invalid_col_values(col,valid)
self.remove_invalid_grid_values(row,col,valid)
self.valid_values[row][col] = valid
def validation(self,row,col,value):
status = 1
for i in range(0,9):
status = -1
break
status = -1
break
else:
continue
if(status == 1):
grid = self.get_grid_cells(row,col)
status = -1
break
else:
continue
return status
def get_cell_loc(self,valid_value):
mini = 10
row_loc = -1
col_loc = -1
set_values = valid_value[row][col]
if set_values == None:
continue
else:
length = len(set_values)
mini = length
row_loc = row
col_loc = col
return(row_loc,col_loc)
#after filling a cell check for conflicts if conflict, try another value
def bactracking(self,valid_value):
status = 1
else:
status = self.validation(row,col,num)
if status == -1:
continue
else:
old_value = self.sudoku[row][col]
self.set_solved_value(row,col)
#time.sleep(0.1)
new_valid_val = np.copy(valid_value)
if status == 1:
break
else:
self.sudoku[row][col] = old_value
self.del_cell_value(row,col)
return status
def button_connector(self):
self.pushButton.setEnabled(False)
self.pushButton_2.setEnabled(False)
self.disable_input()
self.read_input()
self.fixed_empty_cell()
self.change_label_style()
self.get_valid_value()
self.bactracking(self.valid_values)
self.pushButton_2.setEnabled(True)
def clear_sudoku(self):
self.valid_values = np.empty([9,9], dtype = set) #Matrix Set for storing valid values
layout = self.GridLayout.itemAtPosition(row,col)
text_box = layout.widget()
text_box.setText("")
old_style = text_box.styleSheet()
text_box.setStyleSheet(new_style)
text_box.setReadOnly(False)
self.app.processEvents()
def clear_button(self):
self.pushButton.setEnabled(False)
self.pushButton_2.setEnabled(False)
self.clear_sudoku()
self.pushButton.setEnabled(True)
self.pushButton_2.setEnabled(True)
def main():
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = sudoku_gui(app)
ui.setupUi(MainWindow)
ui.pushButton.clicked.connect(ui.button_connector)
ui.pushButton_2.clicked.connect(ui.clear_button)
MainWindow.show()
sys.exit(app.exec_())
main()