Add backtrack word search in matrix (#8005)

* add backtracking word search

* updating DIRECTORY.md

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* review notes fixes

* additional fixes

* add tests

* small cleanup

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* small cleanup 2

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update backtracking/word_search.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update backtracking/word_search.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update backtracking/word_search.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update backtracking/word_search.py

Co-authored-by: Christian Clauss <cclauss@me.com>

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
This commit is contained in:
Alexander Pantyukhin 2022-11-29 22:28:47 +04:00 committed by GitHub
parent 47bf3f58e0
commit 6a86fe4867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 161 additions and 0 deletions

View File

@ -33,6 +33,7 @@
* [Rat In Maze](backtracking/rat_in_maze.py)
* [Sudoku](backtracking/sudoku.py)
* [Sum Of Subsets](backtracking/sum_of_subsets.py)
* [Word Search](backtracking/word_search.py)
## Bit Manipulation
* [Binary And Operator](bit_manipulation/binary_and_operator.py)

160
backtracking/word_search.py Normal file
View File

@ -0,0 +1,160 @@
"""
Author : Alexander Pantyukhin
Date : November 24, 2022
Task:
Given an m x n grid of characters board and a string word,
return true if word exists in the grid.
The word can be constructed from letters of sequentially adjacent cells,
where adjacent cells are horizontally or vertically neighboring.
The same letter cell may not be used more than once.
Example:
Matrix:
---------
|A|B|C|E|
|S|F|C|S|
|A|D|E|E|
---------
Word:
"ABCCED"
Result:
True
Implementation notes: Use backtracking approach.
At each point, check all neighbors to try to find the next letter of the word.
leetcode: https://leetcode.com/problems/word-search/
"""
def word_exists(board: list[list[str]], word: str) -> bool:
"""
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCCED")
True
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "SEE")
True
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCB")
False
>>> word_exists([["A"]], "A")
True
>>> word_exists([["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","B"],
... ["A","A","A","A","B","A"]],
... "AAAAAAAAAAAAABB")
False
>>> word_exists([["A"]], 123)
Traceback (most recent call last):
...
ValueError: The word parameter should be a string of length greater than 0.
>>> word_exists([["A"]], "")
Traceback (most recent call last):
...
ValueError: The word parameter should be a string of length greater than 0.
>>> word_exists([[]], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
>>> word_exists([], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
>>> word_exists([["A"], [21]], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
"""
# Validate board
board_error_message = (
"The board should be a non empty matrix of single chars strings."
)
if not isinstance(board, list) or len(board) == 0:
raise ValueError(board_error_message)
for row in board:
if not isinstance(row, list) or len(row) == 0:
raise ValueError(board_error_message)
for item in row:
if not isinstance(item, str) or len(item) != 1:
raise ValueError(board_error_message)
# Validate word
if not isinstance(word, str) or len(word) == 0:
raise ValueError(
"The word parameter should be a string of length greater than 0."
)
traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)]
len_word = len(word)
len_board = len(board)
len_board_column = len(board[0])
# Returns the hash key of matrix indexes.
def get_point_key(row: int, column: int) -> int:
"""
>>> len_board=10
>>> len_board_column=20
>>> get_point_key(0, 0)
200
"""
return len_board * len_board_column * row + column
# Return True if it's possible to search the word suffix
# starting from the word_index.
def exits_word(
row: int, column: int, word_index: int, visited_points_set: set[int]
) -> bool:
"""
>>> board=[["A"]]
>>> word="B"
>>> exits_word(0, 0, 0, set())
False
"""
if board[row][column] != word[word_index]:
return False
if word_index == len_word - 1:
return True
for direction in traverts_directions:
next_i = row + direction[0]
next_j = column + direction[1]
if not (0 <= next_i < len_board and 0 <= next_j < len_board_column):
continue
key = get_point_key(next_i, next_j)
if key in visited_points_set:
continue
visited_points_set.add(key)
if exits_word(next_i, next_j, word_index + 1, visited_points_set):
return True
visited_points_set.remove(key)
return False
for i in range(len_board):
for j in range(len_board_column):
if exits_word(i, j, 0, {get_point_key(i, j)}):
return True
return False
if __name__ == "__main__":
import doctest
doctest.testmod()