0% found this document useful (0 votes)
30 views

CS480 Assignment-3

This document contains code for solving medium and hard crossword puzzles using forward checking as a heuristic to reduce the search space. It includes code for creating variable objects from the puzzle grid, making constraints between variables, implementing forward checking, and using backtracking search to solve the puzzles. Execution times are reported for each puzzle solved. The code is available in a GitHub repository with readme files explaining how to run it.

Uploaded by

ashish.verma.gcp
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views

CS480 Assignment-3

This document contains code for solving medium and hard crossword puzzles using forward checking as a heuristic to reduce the search space. It includes code for creating variable objects from the puzzle grid, making constraints between variables, implementing forward checking, and using backtracking search to solve the puzzles. Execution times are reported for each puzzle solved. The code is available in a GitHub repository with readme files explaining how to run it.

Uploaded by

ashish.verma.gcp
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

CS480 Assignment -3

Analysis: -
The below code is available at git repo =>
https://github.com/ashishodu2023/CrosswordsolverAi.git with readme files with all
the instructions on how to execute the cross wore puzzle. The medium and hard
crossword puzzle uses Forward Checking as of the Heuristics to reduce the domain
and search space. The execution time for each of the puzzle is given after code
completion with results of each of the execution.
Forward checking detects the inconsistency earlier than simple backtracking and
thus it allows branches of the search tree that will lead to failure to be pruned
earlier than with simple backtracking. This reduces the search tree and the overall
amount of work done.

Folder Structure: -
Grid Medium Structure: -
##-------###
##-#####-##-
##-#####-##-
##-##-----#-
##-####-###-
####-##-----
####-##-#-#-
#-#-----#-##
#-##-##-#-##
------###-##
#-##-####-##

Grid Hard Structure: -


##---###---##
#-----#-----#
#-----#-----#
---#-----#---
----#---#----
----#---#----
#----###----#
##----#----##
###-------###
####-----####
#####ash#####
######-######

ReadMe :-

Tiny Puzzle
import time

def CrosswordSolverTiny(grid, word_data):

# Function to check if a word can be placed in the given


direction at the specified position
def CheckHorizontalVertical(word, row, col, direction):
if direction == "ACROSS":
if col + len(word) > len(grid[0]):
return False
for i in range(len(word)):
if grid[row][col + i] != " " and grid[row][col
+ i] != word[i]:
return False
if grid[row][col + i] == "#":
return False
else: # direction == "down"
if row + len(word) > len(grid):
return False
for i in range(len(word)):
if grid[row + i][col] != " " and grid[row +
i][col] != word[i]:
return False
if grid[row + i][col] == "#":
return False
return True

# Function to set a word in the grid at the specified


position and direction
def SetWord(word, row, col, direction):
if direction == "ACROSS":
for i in range(len(word)):
grid[row][col + i] = word[i]
else: # direction == "down"
for j in range(len(word)):
grid[row + j][col] = word[j]

# Function to clear a word from the grid at the specified


position and direction
def GetWord(word, row, col, direction):
if direction == "ACROSS":
for i in range(len(word)):
grid[row][col + i] = " "
else: # direction == "down"
for j in range(len(word)):
grid[row + j][col] = " "
# Recursive function to solve the crossword puzzle
def Solve(grid, word_data):
if not word_data:
return True

variable, start_cell, domain = word_data[0]


for row, col in start_cell:
for word in list(domain):
if CheckHorizontalVertical(word, row, col,
variable[1:]):
SetWord(word, row, col, variable[1:])
domain.remove(word)
if Solve(grid, word_data[1:]):
return True
GetWord(word, row, col, variable[1:])
domain.add(word)

return False

# Main function to solve the crossword puzzle and print the


result
start_time = time.time()
if Solve(grid, word_data):
for row in grid:
print("".join(row))
end_time = (time.time() - start_time)
print(f'\n---Time taken for code execution %s seconds -
--{end_time}')
else:
print("No solution found")

def main():
# Example crossword grid and word data
crossword_grid = [
[" ", " ", " ", " ", " "],
["#", "#", " ", "#", " "],
["#", " ", " ", " ", " "],
[" ", "#", " ", " ", " "],
[" ", " ", " ", " ", " "],
[" ", "#", "#", " ", "#"],
]

word_data = [
("1ACROSS", [(0, 0)], {"HOSES", "LASER", "SAILS",
"SHEET", "STEER"}),
("4ACROSS", [(2, 1)], {"HEEL", "HIKE", "KEEL", "KNOT",
"LINE"}),
("7ACROSS", [(3, 2)], {"AFT", "ALE", "EEL", "LEE",
"TIE"}),
("8ACROSS", [(4, 0)], {"HOSES", "LASER", "SAILS",
"SHEET", "STEER"}),
("2DOWN", [(0, 2)], {"HOSES", "LASER", "SAILS",
"SHEET", "STEER"}),
("3DOWN", [(0, 4)], {"HOSES", "LASER", "SAILS",
"SHEET", "STEER"}),
("5DOWN", [(2, 3)], {"HEEL", "HIKE", "KEEL", "KNOT",
"LINE"}),
("6DOWN", [(3, 0)], {"AFT", "ALE", "EEL", "LEE",
"TIE"}),
]

# Call the crossword solver function


CrosswordSolverTiny(crossword_grid, word_data)

if __name__ == '__main__':
main()

Result of the above code execution.


(msds)
C:\Users\Ashish\PycharmProjects\CrossWordSolver>c:/Users/Ashish/msds/Scripts/python.exe
c:/Users/Ashish/PycharmProjects/CrossWordSolver/CrosswordSolverTiny.py
HOHES
##O#T
#HSKE
A#EEE
LASER
E##L#

---Time taken for code execution %s seconds ---0.00024819374084472656

Medium Puzzle
import time

# Class to represent a variable in the crossword puzzle


class Variable:
def __init__(self, direction, row, col, length, domain):
self.word = ""
self.direction = direction
self.row = row
self.col = col
self.length = length
self.domain = domain
self.removed_domain = {}

# Function to display the crossword board


def ShowBoard(grid, assignment):
board = grid.split("\n")
board = [list(row) for row in board]
for v in assignment:
val = assignment[v]
if v.direction == "horizontal":
for i in range(v.length):
board[v.row][v.col + i] = val[i]
else:
for i in range(v.length):
board[v.row + i][v.col] = val[i]
for row in board:
print(" ".join(map(str, row)))

# Function to check if a given assignment satisfies the


constraints
def SatisfyConstraint(V, assignment, Vx, val):
for v in V:
Cxv = MakeConstraint(Vx, v)
if v != Vx and v in assignment and Cxv:
if val[Cxv[0]] != assignment[v][Cxv[1]]:
return False
return True

# Function to find an unassigned variable


def UnassignedVariable(V, assignment):
unassigned = []
for v in V:
if v not in assignment:
unassigned.append(v)

unassigned.sort(key=lambda x: len(x.domain))
return unassigned[0]

# Function to reduce the domain of variables based on


constraints
def ForwardChecking(V, assignment, Vx, val):
for v in V:
Cxv = MakeConstraint(Vx, v)
if v != Vx and v not in assignment and Cxv:
v.domain = [word for word in v.domain if
val[Cxv[0]] == word[Cxv[1]]]

# Function to restore the original domain of variables


def OriginalDomain(V, assignment, Vx, val):
for v in V:
Cxv = MakeConstraint(Vx, v)
if v != Vx and v not in assignment and Cxv:
if v in Vx.removed_domain:
for word in Vx.removed_domain[v]:
if val[Cxv[0]] != word[Cxv[1]]:
v.domain.append(word)
Vx.removed_domain[v].remove(word)

# Backtracking algorithm to solve the crossword puzzle with


forward checking
def BacktrackingAlgo(V, assignment):
if len(assignment) == len(V):
return True
Vx = UnassignedVariable(V, assignment)
for val in Vx.domain:
if val in assignment.values():
continue
if SatisfyConstraint(V, assignment, Vx, val):
assignment[Vx] = val
ForwardChecking(V, assignment, Vx, val)
result = BacktrackingAlgo(V, assignment)
if result:
return True
assignment.pop(Vx, None)
OriginalDomain(V, assignment, Vx, val)
return False

# Function to create a constraint between two variables


def MakeConstraint(Vx, Vy):
constraint = ()
if Vx.direction != Vy.direction:
if Vx.direction == "horizontal":
if Vy.col >= Vx.col and Vy.col <= Vx.col +
Vx.length - 1:
if Vx.row >= Vy.row and Vx.row <= Vy.row +
Vy.length - 1:
constraint = (Vy.col - Vx.col, Vx.row -
Vy.row)
else:
if Vy.row >= Vx.row and Vy.row <= Vx.row +
Vx.length - 1:
if Vx.col >= Vy.col and Vx.col <= Vy.col +
Vy.length - 1:
constraint = (Vy.row - Vx.row, Vx.col -
Vy.col)
return constraint

# Function to create arcs between variables based on


constraints
def MakeArc(V):
arcs = []
for i in range(len(V)):
for j in range(i + 1, len(V)):
if i != j:
Cij = MakeConstraint(V[i], V[j])
if len(Cij) > 0:
arcs.append((V[i], V[j], Cij))
return arcs
# Function to create variable objects from the crossword grid
and word list
def MakeVariables(grid, words):
variables = []
board = grid.split("\n")
for row in range(len(board)):
for col in range(len(board[row])):
if board[row][col] == "-":
if col == 0 or board[row][col - 1] == "#":
length = 0
for i in range(col, len(board[row])):
if board[row][i] == "-":
length += 1
else:
break
if length == 1:
condition = True
try:
if board[row][col + 1] == "-":
condition = False
except IndexError:
pass
try:
if board[row][col - 1] == "-" and
col - 1 >= 0:
condition = False
except IndexError:
pass
try:
if board[row - 1][col] == "-" and
row - 1 >= 0:
condition = False
except IndexError:
pass
try:
if board[row + 1][col] == "-":
condition = False
except IndexError:
pass
if condition:
domain = []
for word in words:
if len(word) == length:
domain.append(word)
variables.append(Variable(
"horizontal",
row,
col,
length,
domain
))

if length > 1:
domain = []
for word in words:
if len(word) == length:
domain.append(word)
variables.append(Variable(
"horizontal",
row,
col,
length,
domain
))
if row == 0 or board[row - 1][col] == "#":
length = 0
for i in range(row, len(board)):
if board[i][col] == "-":
length += 1
else:
break
if length > 1:
domain = []
for word in words:
if len(word) == length:
domain.append(word)
variables.append(Variable(
"vertical",
row,
col,
length,
domain
))
return variables

# Function to get the crossword grid from a file


def GetGrid(file_path):
with open(file_path) as file:
return file.read()

# Main function to solve the crossword puzzle


def main():
assignment = {}
grid = GetGrid("grid_medium.txt")
words = GetGrid("Words.txt").splitlines()
words = [word.upper() for word in words]

variables = MakeVariables(grid, words)


variables.sort(key=lambda x: len(x.domain))
BacktrackingAlgo(variables, assignment)
ShowBoard(grid, assignment)

if __name__ == "__main__":
start_time = time.time()
main()
print("\n---Time taken for code execution %s seconds ---" %
(time.time() - start_time))

Result of the above code execution


(msds)
C:\Users\Ashish\PycharmProjects\CrossWordSolver>c:/Users/Ashish/msds/Scripts/python.exe
c:/Users/Ashish/PycharmProjects/CrossWordSolver/CrosswordSovlerMedium.py
##ALFALFA###
##B#####B##A
##A#####B##F
##C##ABBEY#I
##K####A###E
####A##BABEL
####F##B#A#D
#B#AFOUL#B##
#A##A##E#O##
ABATIS###O##
#E##R####N##

---Time taken for code execution 0.018278837203979492 seconds ---


Hard Puzzle

import time
import nltk
from collections import deque

# Load words from a file and filter for English words


def LoadDictionary(file_path):
with open(file_path, 'r') as file:
all_words = set(word.strip().lower() for word in
file.readlines())

english_words = set(w.lower() for w in


nltk.corpus.words.words())

return all_words.intersection(english_words)

# Check if a given word is valid based on the loaded dictionary


def IsValidWord(word, dictionary):
return word.lower() in dictionary

# Check if a word can be placed on the board at a specific


location and direction
def IsValidLocation(board, row, col, word, direction,
dictionary):
if direction == 'horizontal':
for i in range(len(word)):
if (
col + i >= 13 or
(board[row][col + i] != 0 and board[row][col +
i] != word[i]) or
not IsValidWord(word[i], dictionary)
):
return False
elif direction == 'vertical':
for i in range(len(word)):
if (
row + i >= 12 or
(board[row + i][col] != 0 and board[row +
i][col] != word[i]) or
not IsValidWord(word[i], dictionary)
):
return False
else:
return False

return True

# Solve the puzzle using backtracking


def SolvePuzzleBacktracking(board, dictionary,
remaining_words):
empty = EmptyLocations(board)
if not empty:
return True

row, col = empty

for word in remaining_words[row][col].copy():


for direction in ['horizontal', 'vertical']:
if IsValidLocation(board, row, col, word,
direction, dictionary):
PlaceWords(board, row, col, word, direction)
# Update the remaining words after placing a
word
UpdateWords(board, remaining_words, dictionary)

if SolvePuzzleBacktracking(board, dictionary,
remaining_words):
return True

RemoveWords(board, row, col, word, direction)

return False

# Update the set of remaining words for each empty cell on the
board
def UpdateWords(board, remaining_words, dictionary):
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == 0:
remaining_words[i][j] = GetValidWords(board, i,
j, dictionary)

# Get the set of valid words for a specific location on the


board
def GetValidWords(board, row, col, dictionary):
valid_words = set()

for word in dictionary:


for direction in ['horizontal', 'vertical']:
if IsValidLocation(board, row, col, word,
direction, dictionary):
valid_words.add(word)

return valid_words

# Find the first empty location on the board


def EmptyLocations(board):
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == 0:
return (i, j)
return None

# Place a word on the board at a specific location and


direction
def PlaceWords(board, row, col, word, direction):
if direction == 'horizontal':
for i in range(len(word)):
board[row][col + i] = word[i]
elif direction == 'vertical':
for i in range(len(word)):
board[row + i][col] = word[i]

# Remove a word from the board at a specific location and


direction
def RemoveWords(board, row, col, word, direction):
if direction == 'horizontal':
for i in range(len(word)):
board[row][col + i] = 0
elif direction == 'vertical':
for i in range(len(word)):
board[row + i][col] = 0

# Load the puzzle from a file


def LoadGrid(file_path):
with open(file_path, 'r') as file:
return [list(line.strip()) for line in
file.readlines()]

if __name__ == "__main__":
start_time = time.time()

# Load puzzle and dictionary


pattern = LoadGrid("grid_hard.txt")
dictionary = LoadDictionary("Words.txt")

# Initialize the puzzle, replacing '-' with 0


puzzle = [[0 if cell == '-' else cell for cell in row] for
row in pattern]

# Initialize the remaining_words variable


remaining_words = [
[GetValidWords(puzzle, i, j, dictionary) if
puzzle[i][j] == 0 else set() for j in range(13)] for i in
range(12)
]

# Solve the puzzle and print the result


if SolvePuzzleBacktracking(puzzle, dictionary,
remaining_words):
for row in puzzle:
print(" ".join(map(str, row)))
print("\n---Time taken for code execution %s seconds --
-" % (time.time() - start_time))
else:
print("No solution exists.")

Results of the above code execution.


(msds)
C:\Users\Ashish\PycharmProjects\CrossWordSolver>c:/Users/Ashish/msds/Scripts/python.exe
c:/Users/Ashish/PycharmProjects/CrossWordSolver/CrosswordSolverHard.py
##gng###gng##
#goeab#baeog#
#obeno#onebo#
nbb#grnrg#bbn
eblb#aea#blbe
eleo#xex#oele
#eorg###groe#
##xaan#naax##
###xneoenx###
####gexeg####
#####ash#####
######g######

---Time taken for code execution 23.934542894363403 seconds ---

You might also like