From 00c2ec6d102e9b62ed40ac0e77ee2bba6070c0b4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Mon, 30 Sep 2024 20:46:54 +0530 Subject: [PATCH 01/10] Add word ladder algorithm in backtracking --- backtracking/word_ladder.py | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 backtracking/word_ladder.py diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py new file mode 100644 index 000000000000..9066d5a678a2 --- /dev/null +++ b/backtracking/word_ladder.py @@ -0,0 +1,71 @@ +""" +Word Ladder is + +Wikipedia: https://en.wikipedia.org/wiki/Word_ladder +""" + + +from collections import deque + + +def word_ladder(beginWord: str, endWord: str, wordList: list[str]) -> int: + """ + Solve the Word Ladder problem using Breadth-First Search (BFS). + + Parameters: + beginWord (str): The word from which the transformation starts. + endWord (str): The target word for transformation. + wordList (list[str]): The list of valid words for transformation. + + Returns: + int: The minimum number of transformations required to reach endWord from beginWord. + Returns 0 if there is no valid transformation. + + Example: + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) + 5 + + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) + 0 + + >>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) + 4 + + >>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"]) + 4 + """ + + # Step 1: Convert the wordList to a set for faster lookup + word_set = set(wordList) + + # Step 2: If endWord is not in the wordList, return 0 (impossible transformation) + if endWord not in word_set: + return 0 + + # Step 3: Initialize the BFS queue + # Each element in the queue will be a tuple: (current_word, current_depth) + # The depth starts at 1 (including the initial word) + queue = deque([(beginWord, 1)]) + + # Step 4: Perform BFS + while queue: + current_word, depth = queue.popleft() + + # If the current word is the endWord, return the current depth + if current_word == endWord: + return depth + + # Generate all possible transformations of the current_word + for i in range(len(current_word)): + for c in "abcdefghijklmnopqrstuvwxyz": # Try changing each letter + transformed_word = current_word[:i] + c + current_word[i + 1:] + + # If the transformed word is in the word set, it's a valid transformation + if transformed_word in word_set: + # Remove from the set to prevent revisiting + word_set.remove(transformed_word) + # Add the transformed word to the queue with an incremented depth + queue.append((transformed_word, depth + 1)) + + # If no transformation is found, return 0 + return 0 From 33fcf6c6344302d8f74104fc43d8702edab94844 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Mon, 30 Sep 2024 20:51:40 +0530 Subject: [PATCH 02/10] Improve comments and implement ruff checks --- backtracking/word_ladder.py | 65 ++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 9066d5a678a2..369fc33025d7 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -1,71 +1,76 @@ """ -Word Ladder is +Word Ladder is a classic problem in computer science. +The problem is to transform a start word into an end word +by changing one letter at a time. +Each intermediate word must be a valid word from a given list of words. +The goal is to find the minimum length transformation sequence +from the start word to the end word. Wikipedia: https://en.wikipedia.org/wiki/Word_ladder """ - from collections import deque -def word_ladder(beginWord: str, endWord: str, wordList: list[str]) -> int: +def word_ladder(begin_word: str, end_word: str, word_list: list[str]) -> list[str]: """ - Solve the Word Ladder problem using Breadth-First Search (BFS). + Solve the Word Ladder problem using Breadth-First Search (BFS) and return + the list of transformations from begin_word to end_word. Parameters: - beginWord (str): The word from which the transformation starts. - endWord (str): The target word for transformation. - wordList (list[str]): The list of valid words for transformation. + begin_word (str): The word from which the transformation starts. + end_word (str): The target word for transformation. + word_list (list[str]): The list of valid words for transformation. Returns: - int: The minimum number of transformations required to reach endWord from beginWord. - Returns 0 if there is no valid transformation. + list[str]: The list of transformations from begin_word to end_word. + Returns an empty list if there is no valid transformation. Example: >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) - 5 + ['hit', 'hot', 'dot', 'dog', 'cog'] >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) - 0 + [] >>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) - 4 + ['lead', 'load', 'goad', 'gold'] >>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"]) - 4 + ['game', 'came', 'cade', 'code'] """ - # Step 1: Convert the wordList to a set for faster lookup - word_set = set(wordList) + # Step 1: Convert the word_list to a set for faster lookup + word_set = set(word_list) - # Step 2: If endWord is not in the wordList, return 0 (impossible transformation) - if endWord not in word_set: - return 0 + # Step 2: If end_word is not in the word_list, return an empty list + if end_word not in word_set: + return [] # Step 3: Initialize the BFS queue - # Each element in the queue will be a tuple: (current_word, current_depth) - # The depth starts at 1 (including the initial word) - queue = deque([(beginWord, 1)]) + # Each element in the queue will be a tuple: (current_word, current_path) + # current_path tracks the sequence of transformations + queue = deque([(begin_word, [begin_word])]) # Step 4: Perform BFS while queue: - current_word, depth = queue.popleft() + current_word, path = queue.popleft() - # If the current word is the endWord, return the current depth - if current_word == endWord: - return depth + # If the current word is the end_word, return the path + if current_word == end_word: + return path # Generate all possible transformations of the current_word for i in range(len(current_word)): for c in "abcdefghijklmnopqrstuvwxyz": # Try changing each letter transformed_word = current_word[:i] + c + current_word[i + 1:] - # If the transformed word is in the word set, it's a valid transformation + # valid transformation: If the transformed word is in the word set if transformed_word in word_set: # Remove from the set to prevent revisiting word_set.remove(transformed_word) - # Add the transformed word to the queue with an incremented depth - queue.append((transformed_word, depth + 1)) + # Add the transformed word to the queue with the updated path + queue.append((transformed_word, [*path, transformed_word])) - # If no transformation is found, return 0 - return 0 + # If no transformation is found, return an empty list + return [] From 34339f45d97b102528939c22aeb9517ddcfa386f Mon Sep 17 00:00:00 2001 From: Hardvan Date: Mon, 30 Sep 2024 15:22:01 +0000 Subject: [PATCH 03/10] updating DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 955001e2aa23..ae76d78f2350 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -22,6 +22,7 @@ * [Rat In Maze](backtracking/rat_in_maze.py) * [Sudoku](backtracking/sudoku.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py) + * [Word Ladder](backtracking/word_ladder.py) * [Word Search](backtracking/word_search.py) ## Bit Manipulation From 0c48c5dcc687f8daaf6ea725f5276c3d84eb4c5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:23:13 +0000 Subject: [PATCH 04/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backtracking/word_ladder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 369fc33025d7..d83f17a86c62 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -63,7 +63,7 @@ def word_ladder(begin_word: str, end_word: str, word_list: list[str]) -> list[st # Generate all possible transformations of the current_word for i in range(len(current_word)): for c in "abcdefghijklmnopqrstuvwxyz": # Try changing each letter - transformed_word = current_word[:i] + c + current_word[i + 1:] + transformed_word = current_word[:i] + c + current_word[i + 1 :] # valid transformation: If the transformed word is in the word set if transformed_word in word_set: From b64db78ea6bcdc18bd81618ece5341173db589ce Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Mon, 30 Sep 2024 21:58:29 +0530 Subject: [PATCH 05/10] Change BFS to Backtracking --- backtracking/word_ladder.py | 58 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 369fc33025d7..65ac54720147 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -3,18 +3,17 @@ The problem is to transform a start word into an end word by changing one letter at a time. Each intermediate word must be a valid word from a given list of words. -The goal is to find the minimum length transformation sequence +The goal is to find a transformation sequence from the start word to the end word. Wikipedia: https://en.wikipedia.org/wiki/Word_ladder """ -from collections import deque - -def word_ladder(begin_word: str, end_word: str, word_list: list[str]) -> list[str]: +def word_ladder_backtrack(begin_word: str, end_word: str, + word_list: list[str]) -> list[str]: """ - Solve the Word Ladder problem using Breadth-First Search (BFS) and return + Solve the Word Ladder problem using Backtracking and return the list of transformations from begin_word to end_word. Parameters: @@ -27,16 +26,16 @@ def word_ladder(begin_word: str, end_word: str, word_list: list[str]) -> list[st Returns an empty list if there is no valid transformation. Example: - >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) - ['hit', 'hot', 'dot', 'dog', 'cog'] + >>> word_ladder_backtrack("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) + ['hit', 'hot', 'dot', 'lot', 'log', 'cog'] - >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) + >>> word_ladder_backtrack("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) [] - >>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) - ['lead', 'load', 'goad', 'gold'] + >>> word_ladder_backtrack("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) + ['lead', 'lead', 'load', 'goad', 'gold'] - >>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"]) + >>> word_ladder_backtrack("game", "code", ["came", "cage", "code", "cade", "gave"]) ['game', 'came', 'cade', 'code'] """ @@ -47,30 +46,31 @@ def word_ladder(begin_word: str, end_word: str, word_list: list[str]) -> list[st if end_word not in word_set: return [] - # Step 3: Initialize the BFS queue - # Each element in the queue will be a tuple: (current_word, current_path) - # current_path tracks the sequence of transformations - queue = deque([(begin_word, [begin_word])]) - - # Step 4: Perform BFS - while queue: - current_word, path = queue.popleft() - - # If the current word is the end_word, return the path + # Step 3: Backtracking function to find the word ladder + def backtrack(current_word, path): + # Base case: If the current word is the end word, return the path if current_word == end_word: return path - # Generate all possible transformations of the current_word + # Try all possible single-letter transformations for i in range(len(current_word)): - for c in "abcdefghijklmnopqrstuvwxyz": # Try changing each letter + for c in "abcdefghijklmnopqrstuvwxyz": transformed_word = current_word[:i] + c + current_word[i + 1:] - # valid transformation: If the transformed word is in the word set + # If the transformed word is valid and has not been visited if transformed_word in word_set: - # Remove from the set to prevent revisiting + # Remove it from the set to avoid revisiting word_set.remove(transformed_word) - # Add the transformed word to the queue with the updated path - queue.append((transformed_word, [*path, transformed_word])) + # Recur with the new word added to the path + result = backtrack( + transformed_word, [*path, transformed_word]) + if result: # If we found a valid result, return it + return result + # Add it back to the set after exploring this path (backtrack) + word_set.add(transformed_word) + + # If no valid path is found, return an empty list + return [] - # If no transformation is found, return an empty list - return [] + # Step 4: Start backtracking from the begin_word + return backtrack(begin_word, [begin_word]) From e0aca27b991dd7107e4975ba947a55908eee4bfc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:29:51 +0000 Subject: [PATCH 06/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backtracking/word_ladder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 62132a937e59..96f55badd670 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -10,8 +10,9 @@ """ -def word_ladder_backtrack(begin_word: str, end_word: str, - word_list: list[str]) -> list[str]: +def word_ladder_backtrack( + begin_word: str, end_word: str, word_list: list[str] +) -> list[str]: """ Solve the Word Ladder problem using Backtracking and return the list of transformations from begin_word to end_word. @@ -62,8 +63,7 @@ def backtrack(current_word, path): # Remove it from the set to avoid revisiting word_set.remove(transformed_word) # Recur with the new word added to the path - result = backtrack( - transformed_word, [*path, transformed_word]) + result = backtrack(transformed_word, [*path, transformed_word]) if result: # If we found a valid result, return it return result # Add it back to the set after exploring this path (backtrack) From e5d683ff7a722920a6475643fb90b5123cb1436f Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Tue, 1 Oct 2024 15:13:01 +0530 Subject: [PATCH 07/10] Incorporate PR Changes --- backtracking/word_ladder.py | 101 ++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 62132a937e59..25cc01dce863 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -10,8 +10,61 @@ """ -def word_ladder_backtrack(begin_word: str, end_word: str, - word_list: list[str]) -> list[str]: +import string + + +def backtrack(current_word, path, end_word, word_set): + """ + Helper function to perform backtracking to find the transformation + from the current_word to the end_word. + + Parameters: + current_word (str): The current word in the transformation sequence. + path (list[str]): The list of transformations from begin_word to current_word. + end_word (str): The target word for transformation. + word_set (set[str]): The set of valid words for transformation. + + Returns: + list[str]: The list of transformations from begin_word to end_word. + Returns an empty list if there is no valid + transformation from current_word to end_word. + + Example: + >>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"}) + ['hit', 'hot', 'dot', 'lot', 'log', 'cog'] + + >>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"}) + [] + + >>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"}) + ['lead', 'lead', 'load', 'goad', 'gold'] + + >>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"}) + ['game', 'came', 'cade', 'code'] + """ + + # Base case: If the current word is the end word, return the path + if current_word == end_word: + return path + + # Try all possible single-letter transformations + for i in range(len(current_word)): + for c in string.ascii_lowercase: # Try changing each letter + transformed_word = current_word[:i] + c + current_word[i + 1:] + if transformed_word in word_set: + word_set.remove(transformed_word) + # Recur with the new word added to the path + result = backtrack( + transformed_word, [*path, transformed_word], end_word, word_set) + if result: # valid transformation found + return result + word_set.add(transformed_word) # backtrack + + return [] # No valid transformation found + + +def word_ladder(begin_word: str, end_word: str, + word_set: set[str]) -> list[str]: """ Solve the Word Ladder problem using Backtracking and return the list of transformations from begin_word to end_word. @@ -26,51 +79,21 @@ def word_ladder_backtrack(begin_word: str, end_word: str, Returns an empty list if there is no valid transformation. Example: - >>> word_ladder_backtrack("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"]) ['hit', 'hot', 'dot', 'lot', 'log', 'cog'] - >>> word_ladder_backtrack("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) + >>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"]) [] - >>> word_ladder_backtrack("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) + >>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"]) ['lead', 'lead', 'load', 'goad', 'gold'] - >>> word_ladder_backtrack("game", "code", ["came", "cage", "code", "cade", "gave"]) + >>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"]) ['game', 'came', 'cade', 'code'] """ - # Step 1: Convert the word_list to a set for faster lookup - word_set = set(word_list) - - # Step 2: If end_word is not in the word_list, return an empty list - if end_word not in word_set: - return [] - - # Step 3: Backtracking function to find the word ladder - def backtrack(current_word, path): - # Base case: If the current word is the end word, return the path - if current_word == end_word: - return path - - # Try all possible single-letter transformations - for i in range(len(current_word)): - for c in "abcdefghijklmnopqrstuvwxyz": # Try changing each letter - transformed_word = current_word[:i] + c + current_word[i + 1 :] - - # If the transformed word is valid and has not been visited - if transformed_word in word_set: - # Remove it from the set to avoid revisiting - word_set.remove(transformed_word) - # Recur with the new word added to the path - result = backtrack( - transformed_word, [*path, transformed_word]) - if result: # If we found a valid result, return it - return result - # Add it back to the set after exploring this path (backtrack) - word_set.add(transformed_word) - - # If no valid path is found, return an empty list + if end_word not in word_set: # no valid transformation possible return [] - # Step 4: Start backtracking from the begin_word - return backtrack(begin_word, [begin_word]) + # Perform backtracking starting from the begin_word + return backtrack(begin_word, [begin_word], end_word, word_set) From ec9fa0d96224eeb79127a8de5991c9956252b931 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:44:34 +0000 Subject: [PATCH 08/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backtracking/word_ladder.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 25cc01dce863..b1f9bd1fa029 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -9,7 +9,6 @@ Wikipedia: https://en.wikipedia.org/wiki/Word_ladder """ - import string @@ -50,12 +49,13 @@ def backtrack(current_word, path, end_word, word_set): # Try all possible single-letter transformations for i in range(len(current_word)): for c in string.ascii_lowercase: # Try changing each letter - transformed_word = current_word[:i] + c + current_word[i + 1:] + transformed_word = current_word[:i] + c + current_word[i + 1 :] if transformed_word in word_set: word_set.remove(transformed_word) # Recur with the new word added to the path result = backtrack( - transformed_word, [*path, transformed_word], end_word, word_set) + transformed_word, [*path, transformed_word], end_word, word_set + ) if result: # valid transformation found return result word_set.add(transformed_word) # backtrack @@ -63,8 +63,7 @@ def backtrack(current_word, path, end_word, word_set): return [] # No valid transformation found -def word_ladder(begin_word: str, end_word: str, - word_set: set[str]) -> list[str]: +def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]: """ Solve the Word Ladder problem using Backtracking and return the list of transformations from begin_word to end_word. From 131c4301c54546afbd31bc81e4bf6ff8d95d2f61 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Tue, 1 Oct 2024 15:15:11 +0530 Subject: [PATCH 09/10] Add type hints for backtrack function --- backtracking/word_ladder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index 25cc01dce863..6a66aca90ffd 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -13,7 +13,7 @@ import string -def backtrack(current_word, path, end_word, word_set): +def backtrack(current_word: str, path: list[str], end_word: str, word_set: set[str]) -> list[str]: """ Helper function to perform backtracking to find the transformation from the current_word to the end_word. From 59cadca9ed51eb3a1777685f2adc03edcd3a680a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:45:38 +0000 Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- backtracking/word_ladder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backtracking/word_ladder.py b/backtracking/word_ladder.py index fe730fd3ee37..7d9fd00f6669 100644 --- a/backtracking/word_ladder.py +++ b/backtracking/word_ladder.py @@ -12,7 +12,9 @@ import string -def backtrack(current_word: str, path: list[str], end_word: str, word_set: set[str]) -> list[str]: +def backtrack( + current_word: str, path: list[str], end_word: str, word_set: set[str] +) -> list[str]: """ Helper function to perform backtracking to find the transformation from the current_word to the end_word.