From 7e60bac5ff392237460930d73d336d834334d172 Mon Sep 17 00:00:00 2001 From: Nelson Mak <55319541+maknelso@users.noreply.github.com> Date: Sun, 5 Apr 2026 06:38:20 +0200 Subject: [PATCH 1/4] create word search path retrieval with comprehensive doctests --- backtracking/word_search_path.py | 167 +++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 backtracking/word_search_path.py diff --git a/backtracking/word_search_path.py b/backtracking/word_search_path.py new file mode 100644 index 000000000000..d3783e39e707 --- /dev/null +++ b/backtracking/word_search_path.py @@ -0,0 +1,167 @@ +""" +Author : Nelson Mak +Date : April 5, 2026 + +Task: +Given an m x n grid of characters board and a string word, +return the first valid path of coordinates found that matches +the word in the grid. If the word does not exist, return None. + +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: +--------- +|C|A|T| +|X|Y|S| +|A|B|C| +--------- + +Word: "CATS" + +Result: [(0, 0), (0, 1), (0, 2), (1, 2)] + +Implementation notes: +1. Use a backtracking (DFS) approach to explore all possible paths. +2. At each cell, recursively check neighbors, in this question it's (Up, Down, Left, Right). +3. Maintain a 'visited' set for each coordinate + to ensure cells are not reused within the same search branch. +4. If a path matches the word, return the list of coordinates. +5. If a branch fails, 'backtrack' by removing the current cell from + the visited set and the path list to allow for other potential matches. + +Similar leetcode question that returns a bool: https://leetcode.com/problems/word-search/ +""" + + +def get_point_key(len_board: int, len_board_column: int, row: int, column: int) -> int: + """ + Returns the hash key of matrix indexes. + + >>> get_point_key(10, 20, 1, 0) + 200 + """ + + return len_board * len_board_column * row + column + + +def get_word_path( + board: list[list[str]], + word: str, + row: int, + column: int, + word_index: int, + visited_points_set: set[int], + current_path: list[tuple[int, int]], +) -> list[tuple[int, int]] | None: + """ + Return the coordinate path if it's possible to find the word in the grid. + """ + + if board[row][column] != word[word_index]: + return None + + # Track current progress + new_path = current_path + [(row, column)] + + # Base case + if word_index == len(word) - 1: + return new_path + + traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)] + len_board = len(board) + len_board_column = len(board[0]) + 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(len_board, len_board_column, next_i, next_j) + if key in visited_points_set: + continue + + visited_points_set.add(key) + result = get_word_path( + board, word, next_i, next_j, word_index + 1, visited_points_set, new_path + ) + + if result is not None: + return result + + # Backtrack: remove key to try other paths + visited_points_set.remove(key) + + return None + + +def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] | None: + """ + >>> # 1. Word is found case + >>> word_search_path([["C","A","T"],["X","Y","S"],["A","B","C"]], "CATS") + [(0, 0), (0, 1), (0, 2), (1, 2)] + + >>> # 2. Word is not found case + >>> word_search_path([["A","B"],["C","D"]], "ABCD") is None + True + + >>> # 3. Word is a single char + >>> word_search_path([["A"]], "A") + [(0, 0)] + + >>> # 4. Invalid board error (empty list) + >>> word_search_path([], "CAT") + Traceback (most recent call last): + ... + ValueError: The board should be a non empty matrix of single chars strings. + + >>> # 5. Invalid word error + >>> word_search_path([["A"]], 123) + Traceback (most recent call last): + ... + ValueError: The word parameter should be a string of length greater than 0. + """ + # Validation + board_error_message = ( + "The board should be a non empty matrix of single chars strings." + ) + + # Validate board input + 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 input + if not isinstance(word, str) or len(word) == 0: + raise ValueError( + "The word parameter should be a string of length greater than 0." + ) + + rows = len(board) + cols = len(board[0]) + # Main entry point + for r in range(rows): + for c in range(cols): + # Optimization: only trigger recursion if first char in board matches with first char in word input + if board[r][c] == word[0]: + key = get_point_key(rows, cols, r, c) + path_result = get_word_path(board, word, r, c, 0, {key}, []) + if path_result is not None: + return path_result + + return None + + +if __name__ == "__main__": + import doctest + doctest.testmod() \ No newline at end of file From 372721e8590cba02b0390ee1534cfbb799b3b246 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 04:43:03 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backtracking/word_search_path.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backtracking/word_search_path.py b/backtracking/word_search_path.py index d3783e39e707..0f523db5bcec 100644 --- a/backtracking/word_search_path.py +++ b/backtracking/word_search_path.py @@ -4,7 +4,7 @@ Task: Given an m x n grid of characters board and a string word, -return the first valid path of coordinates found that matches +return the first valid path of coordinates found that matches the word in the grid. If the word does not exist, return None. The word can be constructed from letters of sequentially adjacent cells, @@ -24,13 +24,13 @@ Result: [(0, 0), (0, 1), (0, 2), (1, 2)] -Implementation notes: +Implementation notes: 1. Use a backtracking (DFS) approach to explore all possible paths. 2. At each cell, recursively check neighbors, in this question it's (Up, Down, Left, Right). 3. Maintain a 'visited' set for each coordinate to ensure cells are not reused within the same search branch. -4. If a path matches the word, return the list of coordinates. -5. If a branch fails, 'backtrack' by removing the current cell from +4. If a path matches the word, return the list of coordinates. +5. If a branch fails, 'backtrack' by removing the current cell from the visited set and the path list to allow for other potential matches. Similar leetcode question that returns a bool: https://leetcode.com/problems/word-search/ @@ -84,15 +84,15 @@ def get_word_path( key = get_point_key(len_board, len_board_column, next_i, next_j) if key in visited_points_set: continue - + visited_points_set.add(key) result = get_word_path( board, word, next_i, next_j, word_index + 1, visited_points_set, new_path ) - + if result is not None: return result - + # Backtrack: remove key to try other paths visited_points_set.remove(key) @@ -133,7 +133,7 @@ def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] # Validate board input 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) @@ -164,4 +164,5 @@ def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] if __name__ == "__main__": import doctest - doctest.testmod() \ No newline at end of file + + doctest.testmod() From 07f90e444a93bea7ecb84307f6218578e26b7341 Mon Sep 17 00:00:00 2001 From: Nelson Mak <55319541+maknelso@users.noreply.github.com> Date: Sun, 5 Apr 2026 07:22:41 +0200 Subject: [PATCH 3/4] style: fix ruff linting errors and line length --- backtracking/word_search_path.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/backtracking/word_search_path.py b/backtracking/word_search_path.py index 0f523db5bcec..17e7e624dd95 100644 --- a/backtracking/word_search_path.py +++ b/backtracking/word_search_path.py @@ -5,6 +5,7 @@ Task: Given an m x n grid of characters board and a string word, return the first valid path of coordinates found that matches +return the first valid path of coordinates found that matches the word in the grid. If the word does not exist, return None. The word can be constructed from letters of sequentially adjacent cells, @@ -26,7 +27,8 @@ Implementation notes: 1. Use a backtracking (DFS) approach to explore all possible paths. -2. At each cell, recursively check neighbors, in this question it's (Up, Down, Left, Right). +2. At each cell, recursively check neighbors. + case, check: Up, Down, Left, Right. 3. Maintain a 'visited' set for each coordinate to ensure cells are not reused within the same search branch. 4. If a path matches the word, return the list of coordinates. @@ -65,7 +67,7 @@ def get_word_path( return None # Track current progress - new_path = current_path + [(row, column)] + new_path = [*current_path, (row, column)] # Base case if word_index == len(word) - 1: @@ -84,15 +86,12 @@ def get_word_path( key = get_point_key(len_board, len_board_column, next_i, next_j) if key in visited_points_set: continue - visited_points_set.add(key) result = get_word_path( board, word, next_i, next_j, word_index + 1, visited_points_set, new_path ) - if result is not None: return result - # Backtrack: remove key to try other paths visited_points_set.remove(key) @@ -133,7 +132,6 @@ def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] # Validate board input 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) @@ -152,7 +150,8 @@ def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] # Main entry point for r in range(rows): for c in range(cols): - # Optimization: only trigger recursion if first char in board matches with first char in word input + # Optimization: only trigger recursion if first + # char in board matches with first char in word. if board[r][c] == word[0]: key = get_point_key(rows, cols, r, c) path_result = get_word_path(board, word, r, c, 0, {key}, []) From a96685a0372b26a48dca865bb724b5fe5c1db78b Mon Sep 17 00:00:00 2001 From: Nelson Mak <55319541+maknelso@users.noreply.github.com> Date: Sun, 5 Apr 2026 07:28:36 +0200 Subject: [PATCH 4/4] style: fix ruff linting errors and line length --- backtracking/word_search_path.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backtracking/word_search_path.py b/backtracking/word_search_path.py index 17e7e624dd95..6c051153a9b3 100644 --- a/backtracking/word_search_path.py +++ b/backtracking/word_search_path.py @@ -5,7 +5,6 @@ Task: Given an m x n grid of characters board and a string word, return the first valid path of coordinates found that matches -return the first valid path of coordinates found that matches the word in the grid. If the word does not exist, return None. The word can be constructed from letters of sequentially adjacent cells,