Given a 2D board and a word, find if the word exists in the grid.
The word can be constructed from letters of sequentially adjacent
cell, where "adjacent" cells are those horizontally or vertically
neighboring. The same letter cell may not be used more than once.
For example, Given
board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ]
word = "ABCCED", -> returns true
word = "SEE", -> returns true
word = "ABCB", -> returns false
This is a typical DFS + backtracking solution. It compares board[row][col] with word[start]. If they match, change board[row][col] to ‘#’ to mark it as visited. Then move to the next one (i.e. word[start + 1]) and compare it to the current neighbors (doing it by recursion).
Below is my code which is not working. I tried debugging but I feel there is off by one error somewhere which I am not able to track.
class Solution(object):
def exist(self, board, word):
def match(board, word, r, c, index):
if r < 0 or r >= len(board) or c < 0 or c >= len(board[0]) or index < 0 or index > len(word):
return False
if index == len(word):
return True
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for x, y in directions:
tmp = board[r][c]
board[r][c] = "#"
if tmp == word[index] and match(board, word, r+x, r+y, index+1):
return True
board[r][c] = tmp
return False
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
if board == word:
return True
if not board and word or board and not word:
return False
for r in range(len(board)):
for c in range(len(board[0])):
if match(board, word, r, c, 0):
return True
return False
It is fixed by following #jeff suggestions from the comments to this question i.e. "r+y" should be "c+y".
Related
I'm working on algos in python and tried to implement python sudoku solver. After many tries I've decided to look for the solution and decided to use the one listed here
I've tried to play with the code and modify it to accept a local variable sudoku instead of the global one, however it seems like my code solves the sudoku and then replaces the solved sudoku with the unsolved one probably due to the recursion modification.
Here is my modified code:
#Forming the Puzzle Grid
def form_grid(puzzle_string):
global grid
print('The Sudoku Problem')
for i in range(0, len(puzzle_string), 9):
row = puzzle_string[i:i+9]
temp = []
for block in row:
temp.append(int(block))
grid.append(temp)
printGrid()
Function to print the Grid
#Function to print the grid
def printGrid():
global grid
for row in grid:
print(row)
#Function to check if a digit can be placed in the given block
def possible(grid,row,col,digit):
for i in range(0,9):
if grid[row][i] == digit:
return False
for i in range(0,9):
if grid[i][col] == digit:
return False
square_row = (row//3)*3
square_col = (col//3)*3
for i in range(0,3):
for j in range(0,3):
if grid[square_row+i][square_col+j] == digit:
return False
return True
def solve(grid):
for row in range(9):
for col in range(9):
if grid[row][col] == 0:
for digit in range(1,10):
if possible(grid, row,col,digit):
grid[row][col] = digit
solve(grid)
grid[row][col] = 0 #Backtrack step
return grid
puzzle_string = "004300209005009001070060043006002087190007400050083000600000105003508690042910300"
solve(form_grid(puzzle_string))
Can't figure out how to modify the code to accept the sudoku as a parameter while returning the correct result, I've also tried to check for validity in every cal such as:
if 0 not in grid.flatten():
return grid
but that yielded the same result
The main problem is that you were assigning the current cell always back to 0 and checked all possible digits regardless of the result of the recursive solve. you should check if the recursive solve is valid and decide what would be the next step.
The following is a small fix to your code. First, solve should return bool (i.e., True or False) as the pointer to grid remains the same, so you ddon't really need to return it. Then you should use the recursive call to check if a valid solution was found. If not, continue to the next digit, and if you checked all digits, assign 0 back to the current cell and return False. Otherwise (if a valid solution was found) return True.
def solve(grid):
for row in range(9):
for col in range(9):
if grid[row][col] == 0:
valid = False
for digit in range(1, 10):
if possible(grid, row,col,digit):
grid[row][col] = digit
if solve(grid):
return True
grid[row][col] = 0 # Backtrack step
return False
return True
puzzle_string = "004300209005009001070060043006002087190007400050083000600000105003508690042910300"
grid = form_grid(puzzle_string)
printGrid()
solve(grid)
print()
printGrid()
BTW, there were few bugs in your code, such as relating to a global grid but not initializing it anywhere.
Also, solve is not very efficient as in each recursive call you are searching for the next empty cell. Currently is just a 9X9 grid, so it would be fast anyway, but for generalization, or just as a principal, a better way to use the recursive call is that solve would receive the row and col of the next position to check.
Finally, it might be better to remove the global grid and just return it from the form_grid function. the same for printGrid that should get the grid to print. The following is a version with the mentioned modifications:
#Forming the Puzzle Grid
def form_grid(puzzle_string):
grid = []
print('The Sudoku Problem')
for i in range(0, len(puzzle_string), 9):
row = puzzle_string[i:i+9]
temp = []
for block in row:
temp.append(int(block))
grid.append(temp)
return grid
#Function to print the grid
def printGrid(grid):
for row in grid:
print(row)
print()
#Function to check if a digit can be placed in the given block
def possible(grid, row, col, digit):
for i in range(0, 9):
if grid[row][i] == digit:
return False
for i in range(0, 9):
if grid[i][col] == digit:
return False
square_row = (row // 3) * 3
square_col = (col // 3) * 3
for i in range(0, 3):
for j in range(0, 3):
if grid[square_row + i][square_col + j] == digit:
return False
return True
def get_next_position(current_row, current_col):
if current_col == 8:
if current_row == 8:
return None, None
return current_row + 1, 0
return current_row, current_col + 1
def solve(grid, row, col):
if row is None:
return True
next_row, next_col = get_next_position(row, col)
if grid[row][col] != 0:
return solve(grid, next_row, next_col)
for digit in range(1, 10):
if possible(grid, row, col, digit):
grid[row][col] = digit
if solve(grid, next_row, next_col):
return True
grid[row][col] = 0
return False
puzzle_string = "004300209005009001070060043006002087190007400050083000600000105003508690042910300"
grid = form_grid(puzzle_string)
print("Initial grid")
printGrid(grid)
is_solved = solve(grid, 0, 0)
print(f"Solved: {is_solved}")
print("Solved grid")
printGrid(grid)
You could take the following steps:
Remove the global variable and all global statements
Turn your functions into methods of a new Sudoku class
Make from_grid the constructor of that class, i.e. name it __init__
Define self.grid = [] in this constructor. This way grid is an instance member, so it is separate for each instance you create of Sudoku
Remove all grid function parameters, but add self as the first parameter of all these methods, and make sure to reference self.grid where ever you need access to the grid
solve has an algorithmic problem: it returns grid when it has already backtracked a working "move". Instead make solve such that it returns a boolean: True when the solution is found, and False if not, and make sure it does not clear any moves when a solution is found, and does not try any alternatives. That way self.grid will have the solution.
Here are those things implemented, with a few other cosmetic changes:
class Sudoku:
def __init__(self, puzzle_string):
self.grid = [
list(map(int, puzzle_string[i:i+9]))
for i in range(0, len(puzzle_string), 9)
]
#Function to print the grid
def printGrid(self):
for row in self.grid:
print(row)
#Function to check if a digit can be placed in the given block
def possible(self, row, col, digit):
if digit in self.grid[row]:
return False
for i in range(0,9):
if self.grid[i][col] == digit:
return False
square_row = (row//3)*3
square_col = (col//3)*3
for i in range(0,3):
for j in range(0,3):
if self.grid[square_row+i][square_col+j] == digit:
return False
return True
def solve(self):
for row in range(9):
for col in range(9):
if self.grid[row][col] == 0:
for digit in range(1,10):
if self.possible(row, col, digit):
self.grid[row][col] = digit
if self.solve():
return True
self.grid[row][col] = 0 #Backtrack step
return False
return True
puzzle_string = "004300209005009001070060043006002087190007400050083000600000105003508690042910300"
puzzle = Sudoku(puzzle_string)
print('The Sudoku Problem:')
puzzle.printGrid()
puzzle.solve()
if puzzle.solve():
print("Solved:")
puzzle.printGrid()
else:
print("Sorry, no solution found")
I was completing this Leetcode problem: https://leetcode.com/problems/word-search/ and I randomly chose to implement the DFS iteratively with a while loop and a stack but I encountered some inconveniences when backtracking that I wouldn't normally occur if I had done the problem recursively i.e. I could only think of implementing a list (visited_index) to keep track of the indexes I had visited and pop values off to set the boolean matrix visited back to False when backtracking.
from collections import defaultdict
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
starting_points = defaultdict(list)
m, n = len(board), len(board[0])
for i in range(m):
for j in range(n):
starting_points[board[i][j]].append((i,j))
start = starting_points[word[0]]
visited = [[False] * n for _ in range(m)]
stack = []
directions = [(1,0), (0,-1), (-1,0), (0,1)]
for s in start:
stack.append((s[0], s[1], 0))
visited_index = [] # EXTRA LIST USED
while stack:
x, y, count = stack.pop()
while len(visited_index) > count:
i, j = visited_index.pop()
visited[i][j] = False # SETTING BACK TO FALSE WHEN BACKTRACKING
if x < 0 or x >= m or y < 0 or y >= n or visited[x][y] or board[x][y] != word[count]:
continue
else:
visited[x][y] = True
visited_index.append((x,y))
if count + 1 == len(word):
return True
for d in directions:
i, j = x + d[0], y + d[1]
stack.append((i,j, count + 1))
else:
stack.clear()
for i in range(m):
for j in range(n):
visited[i][j] = False
return False
I believe that in a recursive approach I could have reset the the visited boolean value back to False at the end of the function without the need to use an extra list. Does anyone have any suggestions to not introduce an extra data structure when doing an iterative DFS with a stack?
I would keep the parent node on the stack for as long there are children being processed. Then when all children have been processed and you pop the parent from the stack you'll have the right moment to also remove the visited mark for that parent.
One way to implement that idea is to put one more information in the tuple that you put on the stack: the last direction that was taken. You can use that info to look for the next direction to take, and if there is a valid direction available, push the current node back on the stack with that new direction, and then push the corresponding child on the stack. The latter gets some default value for that "previous" direction indication. For example -1.
I reworked your code to align with that idea:
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
stack = []
m, n = len(board), len(board[0])
for i in range(m):
for j in range(n):
if board[i][j] == word[0]:
# 4th member of tuple is previous direction taken. -1 is none.
stack.append((i, j, 1, -1)) # count=1, side=-1
visited = [[False] * n for _ in range(m)]
directions = [(1,0), (0,-1), (-1,0), (0,1)]
while stack:
x, y, count, side = stack.pop()
# perform the success-check here, so it also works for 1-letter words.
if count == len(word):
return True
visited[x][y] = True # will already be True when side > -1
# find next valid direction
found = False
while side < 3 and not found:
side += 1
dx, dy = directions[side]
i, j = x + dx, y + dy
found = 0 <= i < m and 0 <= j < n and not visited[i][j] and board[i][j] == word[count]
if not found: # all directions processed => backtrack
visited[x][y] = False
continue
stack.append((x, y, count, side)) # put node back on stack
stack.append((i, j, count + 1, -1))
return False
I'm trying to write a program to check if a particular word can be made using a given "boggle board". There full details of the challenge are here: Boggle Word Checker.
Basically, the program is supposed find the first letter of a word, on the board, and then check if any of the adjacent letters to it on the board match the next letter of the word.
This is what I've got so far (not pretty I know):
def pos(board, x, y):
# function to get value from a grid, but return None if not on grid,to
# prevent wraparound
if x >= 0 and y >= 0 and x < len(board[0]) and y < len(board):
return board[y][x]
else:
return None
def surrounds(board, x, y):
# make a dictionary which to store the positions and values of adjacent
# letters on board, given a single postision as input
return {
(x-1,y-1) : pos(board,x-1,y-1), #aboveLeft
(x,y-1) : pos(board,x,y-1), #aboveMiddle etc...
(x+1,y-1) : pos(board,x+1,y-1),
(x-1,y) : pos(board,x-1,y),
(x+1,y) : pos(board,x+1,y),
(x-1,y+1) : pos(board,x-1,y+1),
(x,y+1) : pos(board,x,y+1),
(x+1,y+1) : pos(board,x+1,y+1)
}
def find_word(board, word):
# initialise
listOfCoords = []
# find all occurrences of the first letter, and store their board
# position in a list
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == word[0]:
listOfCoords.append([j,i])
print('list of ' + word[0] + 's on board:')
print(listOfCoords)
print()
# if word is only 1 letter long then we can return True at this point
if listOfCoords and len(word) == 1:
return True
# otherwise we move on to look for the second letter
return findnext(board,word,listOfCoords)
def findnext(board, word, mylist):
for x, y in mylist:
print("Current coords: {},{}\n".format(x,y))
surroundings = surrounds(board,x,y)
listFounds = []
for k, v in surroundings.items():
if v == word[1]:
print("{} found at {}".format(v,k))
print()
if len(word) == 2:
print()
return True
listFounds.append(k)
if findnext(board, word[1:], listFounds) == True:
return True
return False
testBoard = [
["E","A","R","A"],
["N","L","E","C"],
["I","A","I","S"],
["B","Y","O","R"]
]
print(find_word(testBoard, "CEREAL"))
However, I've encountered a problem, as the challenge specifies that no position on the board can be used more than once. Therefore, in the above example, the program should return False for "CEREAL", but mine returns True.
I was thinking a way around this could be to use a set, which adds the coordinates to the set once a next letter is found. However I'm a bit lost as to where I would need to create the empty set, and how it would work with all the loops and recursion going on...
For example, let's say we were looking for "CEREAL" on a different board, which has 2 Es adjacent to C. let's say the first path only leads to CER and the other leads to CEREAL. If we go down the CER path first, the positions for it would be added to the set, and I would somehow need to remove them again before it goes down the CEREAL path.
I'm struggling to think how to implement this in my program.
You need to have an array of booleans the size of the board all set to False. When you use a letter and call your function recursively, set the cell to True for the used letter. When returning from the recursive call, set the cell value back to False. You should only use letters with False values indicating that they have not been previously used.
Alternatively, you can replace the used letter by None after keeping the letter in a temporary variable then do the recursive call. Upon the return from the recursive call put back the value of the cell from the temp variable.
Steps:
Find the 1st character match against the board
Once a match is found, do a DFS with backtracking till you completely match the word or exhaust the search due to mismatch.
If complete match is not found in above step, continue scanning the board. (i.e. go to step 1) for the next character that matches on the board.
If a match is found in step 3, report success.
This solution works for NxM board as well.
def is_within_bounds(row, col, row_dim, col_dim):
return row >= 0 and row < row_dim and col >= 0 and col < col_dim
def get_neighbors(row, col, row_dim, col_dim):
for r_offset in (-1, 0, 1):
for c_offset in (-1, 0, 1):
if r_offset == 0 and c_offset == 0:
continue
r_new = row + r_offset
c_new = col + c_offset
if is_within_bounds(r_new, c_new, row_dim, col_dim):
yield (r_new, c_new)
def is_word_found(board, word, row, col, visited):
if word[0] != board[row][col]:
return False
if len(word) == 1:
return True
for neighbor in get_neighbors(row, col, len(board), len(board[0])):
if neighbor not in visited:
visited.add(neighbor)
if is_word_found(board, word[1:], neighbor[0], neighbor[1], visited):
return True
visited.remove(neighbor)
return False
def find_word(board, word):
for row in range(len(board)):
for col in range(len(board[0])):
if word[0] != board[row][col]:
continue
if is_word_found(board, word, row, col, set()):
return True
return False
I'm writing a program to generate sparse crossword/anagram puzzles that should look like this:
-------------T--
-----------OGRE-
---------T---O--
--------FORGET--
---------R------
---------T------
In this case, the words are FORGET, OGRE, TORT, and TROT. The rules for the grid's design are roughly what you'd expect; words have to intersect on the same letter, words can't run adjacent to each other, and all the words supplied have to share a subset of some set of letters. They're anagrams.
The code below implements the grid, and a method to "scan" the grid and insert each word at the first valid position. The first word is inserted at the approximate center of the puzzle.
Unfortunately, the code doesn't actually insert anything beyond the first word, and for the life of me I can't figure out what is wrong with my checks in the word_fits function.
import enum
import io
import itertools
import math
import random
#enum.unique
class Direction(enum.Enum):
ACROSS, DOWN = enum.auto(), enum.auto()
def __str__(self):
return self.name
def get_deltas(self):
return int(self == Direction.DOWN), int(self == Direction.ACROSS)
#staticmethod
def random():
return random.choice(list(Direction))
class Grid:
def __init__(self, height = 16, width = 16):
self.width = width
self.height = height
self.grid = {r: {c: [] for c in range(width)} for r in range(height)}
self.num_words = 0
def get_letter(self, r, c):
if not self.grid[r][c]:
return []
letters = {word[offset] for (word, offset, _) in self.grid[r][c]}
assert(len(letters) == 1)
return letters.pop()
def get_words(self, r, c):
if not self.grid[r][c]:
return []
return [word for word, _, _ in self.grid[r][c]]
def approximate_center(self):
return math.floor(self.height/ 2), math.floor(self.width/ 2)
def word_fits(self, word, r, c, direction):
# Make sure we aren't inserting the word outside the grid
if ((direction == Direction.DOWN and r + len(word) >= self.height) or (direction == Direction.ACROSS and c + len(word) >= self.width)):
return False
# Otherwise we get a KeyError (for being out of bounds) when we check
# the adjacent cells later in this function
if r == 0 or c == 0 or r == self.height-1 or c == self.width-1:
return False
delta_r, delta_c = direction.get_deltas()
# Check that the word doesn't overlap any letters incorrectly
intersects = False
for offset, letter in enumerate(word):
rr = r + offset*delta_r
cc = c + offset*delta_c
other_letter = self.get_letter(rr, cc)
if other_letter:
if letter != other_letter: return False
else: intersects = True
# Check adjacent cells
for delta in [-1, 1]:
rr = r + offset*delta_r + delta*delta_c
cc = c + offset*delta_c + delta*delta_r
if self.grid[rr][cc]:
if any(direction == d for _, _, d in self.grid[rr][cc]):
return False
# if set(self.get_words(r+offset*delta_r, c+offset*delta_c)) & set(self.get_words(rr, cc)):
# return False
if offset == 0:
# delta == -1
# point directly to the left (above) a word placed across (down)
#
# delta == 1
# point directly to the right (below) a word placed across (down)
if delta == -1:
rr = r + delta*delta_r
cc = c + delta*delta_c
elif delta == 1:
rr = r + delta*len(word)*delta_r
cc = c + delta*len(word)*delta_c
if self.grid[rr][cc]:
if any(direction == d for _, _, d in self.grid[rr][cc]):
return False
return True and intersects
def insert_word(self, word, r, c, direction):
assert(isinstance(direction, Direction))
delta_r, delta_c = direction.get_deltas()
for offset, _ in enumerate(word):
self.grid[r + offset*delta_r][c + offset*delta_c].append((word.upper(), offset, direction))
self.num_words += 1
def scan_and_insert_word(self, word):
if self.num_words == 0:
self.insert_word(word, *self.approximate_center(), Direction.random())
return
for d, r, c in itertools.product(list(Direction), range(self.height), range(self.width)):
if self.word_fits(word, r, c, d):
self.insert_word(word, r, c, d)
break
raise ValueError(f""""{word}" could not be inserted.""")
def __str__(self):
output = io.StringIO()
for r in range(self.height):
for c in range(self.width):
if self.grid[r][c]:
# Checks elsewhere ensure that there are no inconsistencies
# in the letters specified by each (word, offset, direction)
# triplet, so we can just grab the first one
word, offset, _ = self.grid[r][c][0]
letter = word[offset]
else:
letter = "-"
output.write(letter)
output.write("\n")
contents = output.getvalue()
output.close()
return contents
random.seed(1)
word_list = ["FORGET", "TROT", "OGRE", "TORT"]
g = Grid()
for word in word_list:
g.scan_and_insert_word(word.upper())
print(g)
There are a lot of checks that still need to be implemented in this code, e.g. checking that the words all share the same say of N letters, for some N, but I'm trying to figure out this bug before moving on.
The issue is in your scan_and_insert_word function:
The first word is inserted with no issues because if self.num_words == 0, you insert the word in the center (approximately) and terminate the function (GOOD).
If that is not true, you try to find a place in the grid where the word can fit. Once a suitable position is found, you insert the word and break (instead of return, which would terminate the function prematurely). Since you are breaking instead of returning, all you're doing is breaking out of the loop, and then raising the ValueError exception even though you've found a perfectly valid spot for the new word.
The fix:
Change the break to a return. Or do this:
def scan_and_insert_word(self, word):
if self.num_words == 0:
self.insert_word(word, *self.approximate_center(), Direction.random())
return
for d, r, c in itertools.product(list(Direction), range(self.height), range(self.width)):
if self.word_fits(word, r, c, d):
self.insert_word(word, r, c, d)
break
else:
raise ValueError(f""""{word}" could not be inserted.""")
Suppose I have a string of lower case letters, e.g.
'ablccmdnneofffpg'
And my aim is to find the longest sequence of the consecutive numbers inside this string which in this case is:
'abcdefg'
The intuitive attempt is to find loop around each letter and obtain the longest sequence starting from that letter. One possible solution is
longest_length = 0
start = None
current_start = 0
while current_start < len(word) - longest_length:
current_length = 1
last_in_sequence = ord(word[current_start])
for i in range(current_start + 1, len(word)):
if ord(word[i]) - last_in_sequence == 1:
current_length += 1
last_in_sequence = ord(word[i])
if current_length > longest_length:
longest_length = current_length
start = current_start
while (current_start < len(word) - 1 and
ord(word[current_start + 1]) - ord(word[current_start]) == 1):
current_start += 1
current_start += 1
Are there any other ways of solving the problem with fewer lines, or even using some pythonic methods?
You could keep track of all subsequences of consecutive characters as seen in the string using a dictionary, and then take the one with the largest length.
Each subsequence is keyed by the next candidate in the alphabet so that once the anticipated candidate is reached in the string, it is used to update the value of the corresponding subsequence in the dictionary and added as a new dictionary value keyed by the next alphabet:
def longest_sequence(s):
d = {}
for x in s:
if x in d:
d[chr(ord(x)+1)] = d[x] + x
else:
d[chr(ord(x)+1)] = x
return max(d.values(), key=len)
print(longest_sequence('ablccmdnneofffpg'))
# abcdefg
print(longest_sequence('ba'))
# b
print(longest_sequence('sblccmtdnneofffpgtuyvgmmwwwtxjyuuz'))
# stuvwxyz
A solution that trades memory for (some) time:
It keeps track of all sequences seen and then at the end prints the longest found (although there could be more than one).
from contextlib import suppress
class Sequence:
def __init__(self, letters=''):
self.letters = letters
self.last = self._next_letter(letters[-1:])
def append(self, letter):
self.letters += letter
self.last = self._next_letter(letter)
def _next_letter(self, letter):
with suppress(TypeError):
return chr(ord(letter) + 1)
return 'a'
def __repr__(self):
return 'Sequence({}, {})'.format(repr(self.letters),
repr(self.last))
word = 'ablccmdnneofffpg'
sequences = []
for letter in word:
for s in sequences:
if s.last == letter:
s.append(letter)
break
else:
sequences.append(Sequence(letters=letter))
sequences = list(sorted(sequences, key=lambda s: len(s.letters), reverse=True))
print(sequences[0].letters)
You are basically asking for the longest increasing subsequence, which is a well-studied problem. Have a look at the pseudo code in Wikipedia.
Similar to MosesKoledoye's solution, but only stores the lengthes for the ordinals of the chars and only builts the solution string in the end. This should therefore be a little more space-efficient:
def longest_seq(s):
d = {}
for c in s:
c, prev_c = ord(c), ord(c) - 1
d[c] = max(d.get(c, 0), d.pop(prev_c, 0) + 1)
c, l = max(d.items(), key=lambda i: i[1])
return ''.join(map(chr, range(c-l+1, c+1)))