You are on page 1of 12

Program Name : MCA

Semester : 5th

Subject : Artificial Intelligence

Group Members : Harsh Kumar (16)

Lav Saini (24)

Assignment : Solving Sudoku problem using


Constraint Satisfaction Search
Sudoku Puzzle Problem
Sudoku is a logic-based, combinatorial number-placement puzzle. In
classic sudoku, the objective is to fill a 9×9 grid with digits so that each
column, each row, and each of the nine 3×3 subgrids that compose the grid
(also called "boxes", "blocks", or "regions") contain all of the digits from 1 to
9. The puzzle setter provides a partially completed grid, which for a
well-posed puzzle has a single solution.

Simple Sudoku puzzles can be solved by using a brute force approach of


trying out every possible number in every empty cell using Backtracking.
But this solution is not a scalable solution for Moderate and Hard puzzles. It
has been established that approximately 5.96 x 11^26 final grids exist, but
trying all of them out is going to take a long long time… more than 13 billion
years! So in order to solve very hard Sudoku puzzles in reasonable time
constraints we need intelligent algorithms. This is where CSP algorithms
step out to shrink this space and speed up the system!

A typical Sudoku puzzle… …and its solution


Constraint Satisfaction Problem
CSP stands for Constraint Satisfaction Problem. Therefore, our main goal
to design such an algorithm is to satisfy all the well-defined constraints
which the problem introduces. CSP techniques can be applied to problems
whose search space causes a combinatorial explosion, meaning as you
move one step further in solving the problem the number of possible
solution states exponentially increases. By applying CSP techniques i.e.,
enforcing strong limitations or constraints on the problem, by doing so, the
invalid solution in the solution space gets eliminated quite early in the
exploration process.

Constraint Satisfaction Search Algorithm


In order to create a CSP algorithm, we need to indicate three properties of
our problem. Variables, Domains, and Constraints. Each variable is a piece
of the problem which needs to be assigned to an appropriate value in order
to solve the problem. Domain indicates which values can be assigned to a
specific variable. And finally, constraints indicate which of the values
existing in the domain could be used at the moment. Let’s try this technique
on our Sudoku problem.

The three properties of the problem are defined as follow:


● Variables: Each empty cell on the board
● Domains: For each cell, a domain is defined as a set of numbers
between 1 and 9 except the numbers which are already used in the
current row, column or 3 x 3 squares.
● Constraints: No redundant numbers in rows, columns and the 3 x 3
squares.
Python Solution:

import numpy as np

class ConstraintSatisfaction(object):

#solver function

def solver(self, sudoku):

coords = []

for x in range(0, 9):

for y in range(0, 9):

if sudoku.puzzle[x][y] == 0:

coords.append((x, y))

return self.backtrack(sudoku, sudoku.puzzle, coords)

# Backtrack algorithm

def backtrack(self, sudoku, puzzle, coords):

if len(coords) == 0: return True, puzzle

# Assign subsequent coords

row_after, column_after = coords[0]

coords_after = coords[1:]
# Recursive approach, updating puzzle board as needed

for x in range(1, 10):

if sudoku.checkCoords(puzzle, row_after, column_after, x):

puzzle[row_after, column_after] = x

result, update_puzzle = self.backtrack(sudoku,


puzzle.copy(), coords_after)

if result: return True, update_puzzle

puzzle[row_after, column_after] = 0

return False, puzzle

class Sudoku(object):

# Initial values

def __init__(self, puzzle, constraint_approach):

self.puzzle = puzzle

self.constraint_approach = constraint_approach

# Essentially calls the solver in ConstraintSatisfaction Class

def solver(self):

coords = []

for x in range(0, 9):

for y in range(0, 9):


if self.puzzle[x][y] == 0:

coords.append((x, y))

return self.constraint_approach.solver(self)

# Ensures general line is valid

def checkLine(self, axis, index, puzzle):

line = []

if axis == 0:

line = puzzle[index, :]

elif axis == 1:

line = puzzle[:, index]

return is_valid

# When called, ensures the 3x3 SubSquare is valid (used in all 9 of


puzzle)

def checkSubSquare(self, row, column, puzzle):

line = []

for x in range(0,3):

for y in range(0, 3):

line.append(puzzle[(row + x), (column + y)])

is_valid = self.checkLine(line)

return is_valid

# Check X/Y axes of Board validity

def checkAxes(self, puzzle, axis, axis_index, num):


for x in range(0, 9):

if axis == 0:

if (puzzle[axis_index][x] == num):

return True

elif axis == 1:

if (puzzle[x][axis_index] == num):

return True

return False

# Check if number already used in

def checkIfUsed(self, puzzle, row, column, num):

for x in range(3):

for y in range(3):

if (puzzle[x + row][y + column] == num):

return True

return False

# Confirms Puzzle has been solved or not by observing each


row/col/SubSquare

def checkDone(self, puzzle):

for x in range(0, 9):

if not self.checkLine(0, x, puzzle):

return False

if not self.checkLine(1, x, puzzle):

return False
row = (int(x / 3)) * 3

column = (x % 3) * 3

if not self.checkSubSquare(row, column, puzzle):

return False

if (0 not in puzzle):

return False

return True

# Same but for X/Y Coords of Board validity

def checkCoords(self, arr, row, column, num):

return not self.checkIfUsed(arr, row - row % 3, column - column %


3, num) and \

not self.checkAxes(arr, 0, row, num) and \

not self.checkAxes(arr, 1, column, num)

# Check rows, then columns, for conflict set pairings

def checkConflicts(self, puzzle, row, column, num):

conflict_set = set()

for x in range(9):

if puzzle[row][x] == num:

conflict_set.add((row + (x * 8)))

for x in range(9):
if puzzle[x][column] == num:

conflict_set.add((x + (column * 8)))

box_row = row - row % 3

box_column = column - column % 3

for x in range(3):

for y in range(3):

if (puzzle[x + box_row][y + box_column] == num):

conflict_set.add(x + box_row + (8 * (y + box_column)))

conflict_set.add(row + (8 * column))

return conflict_set

def printBoard(board):

print("---------------")

for x in range(len(board)):

if (x % 3 == 0) and (x != 0):

print('---------------')

for y in range(len(board[0])):

if (y % 3 == 0) and (y != 0):

print(' | ', end="")

if y == 8:

print(board[x][y])

else :

print(str(board[x][y]) + "", end="")


print("---------------\n")

if __name__ == '__main__':

FILENAME = input("Enter file name: ")

print("\n>>> SudoSolver\nFILE: " + FILENAME + "\nALGO: BACKTRACKING" +


"\n")

board = []

# Read in txt file as long as it's in current directory

with open(FILENAME) as f:

for line in f:

temp_list = [int(elt.strip()) for elt in line.split(',')]

board.append(temp_list)

board = np.array(board)

sudoku = Sudoku(board, ConstraintSatisfaction())

final = ((sudoku.solver()[1:])[0]).tolist()

printBoard(final)
Input Puzzle:
6,0,8,7,0,2,1,0,0

4,0,0,0,1,0,0,0,2

0,2,5,4,0,0,0,0,0

7,0,1,0,8,0,4,0,5

0,8,0,0,0,0,0,7,0

5,0,9,0,6,0,3,0,1

0,0,0,0,0,6,7,5,0

2,0,0,0,9,0,0,0,8

0,0,6,8,0,5,2,0,3

Output:
Input Puzzle:
0,7,0,0,4,2,0,0,0

0,0,0,0,0,8,6,1,0

3,9,0,0,0,0,0,0,7

0,0,0,0,0,4,0,0,9

0,0,3,0,0,0,7,0,0

5,0,0,1,0,0,0,0,0

8,0,0,0,0,0,0,7,6

0,5,4,8,0,0,0,0,0

0,0,0,6,1,0,0,5,0

Output:

You might also like