2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
A Trie/Prefix Tree is a kind of search tree used to provide quick lookup
|
|
|
|
of words/patterns in a set of words. A basic Trie however has O(n^2) space complexity
|
2020-06-16 08:09:19 +00:00
|
|
|
making it impractical in practice. It however provides O(max(search_string, length of
|
|
|
|
longest word)) lookup time making it an optimal approach when space is not an issue.
|
2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class TrieNode:
|
2021-10-22 17:21:41 +00:00
|
|
|
def __init__(self) -> None:
|
2022-10-15 01:07:03 +00:00
|
|
|
self.nodes: dict[str, TrieNode] = {} # Mapping from char to TrieNode
|
2018-10-19 12:48:28 +00:00
|
|
|
self.is_leaf = False
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def insert_many(self, words: list[str]) -> None:
|
2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
Inserts a list of words into the Trie
|
|
|
|
:param words: list of string words
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
for word in words:
|
|
|
|
self.insert(word)
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def insert(self, word: str) -> None:
|
2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
Inserts a word into the Trie
|
|
|
|
:param word: word to be inserted
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
curr = self
|
|
|
|
for char in word:
|
|
|
|
if char not in curr.nodes:
|
|
|
|
curr.nodes[char] = TrieNode()
|
|
|
|
curr = curr.nodes[char]
|
|
|
|
curr.is_leaf = True
|
|
|
|
|
2019-09-13 20:24:25 +00:00
|
|
|
def find(self, word: str) -> bool:
|
2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
Tries to find word in a Trie
|
|
|
|
:param word: word to look for
|
|
|
|
:return: Returns True if word is found, False otherwise
|
|
|
|
"""
|
|
|
|
curr = self
|
|
|
|
for char in word:
|
|
|
|
if char not in curr.nodes:
|
|
|
|
return False
|
|
|
|
curr = curr.nodes[char]
|
|
|
|
return curr.is_leaf
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def delete(self, word: str) -> None:
|
2019-09-13 20:24:25 +00:00
|
|
|
"""
|
|
|
|
Deletes a word in a Trie
|
|
|
|
:param word: word to delete
|
|
|
|
:return: None
|
|
|
|
"""
|
2018-10-19 12:48:28 +00:00
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def _delete(curr: TrieNode, word: str, index: int) -> bool:
|
2019-09-13 20:24:25 +00:00
|
|
|
if index == len(word):
|
|
|
|
# If word does not exist
|
|
|
|
if not curr.is_leaf:
|
|
|
|
return False
|
|
|
|
curr.is_leaf = False
|
|
|
|
return len(curr.nodes) == 0
|
|
|
|
char = word[index]
|
|
|
|
char_node = curr.nodes.get(char)
|
|
|
|
# If char not in current trie node
|
|
|
|
if not char_node:
|
|
|
|
return False
|
|
|
|
# Flag to check if node can be deleted
|
|
|
|
delete_curr = _delete(char_node, word, index + 1)
|
|
|
|
if delete_curr:
|
|
|
|
del curr.nodes[char]
|
|
|
|
return len(curr.nodes) == 0
|
|
|
|
return delete_curr
|
|
|
|
|
|
|
|
_delete(self, word, 0)
|
|
|
|
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def print_words(node: TrieNode, word: str) -> None:
|
2018-10-19 12:48:28 +00:00
|
|
|
"""
|
|
|
|
Prints all the words in a Trie
|
|
|
|
:param node: root node of Trie
|
|
|
|
:param word: Word variable should be empty at start
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
if node.is_leaf:
|
2019-09-13 20:24:25 +00:00
|
|
|
print(word, end=" ")
|
2018-10-19 12:48:28 +00:00
|
|
|
|
|
|
|
for key, value in node.nodes.items():
|
|
|
|
print_words(value, word + key)
|
|
|
|
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def test_trie() -> bool:
|
2019-09-13 20:24:25 +00:00
|
|
|
words = "banana bananas bandana band apple all beast".split()
|
2018-10-19 12:48:28 +00:00
|
|
|
root = TrieNode()
|
|
|
|
root.insert_many(words)
|
2019-09-13 20:24:25 +00:00
|
|
|
# print_words(root, "")
|
|
|
|
assert all(root.find(word) for word in words)
|
|
|
|
assert root.find("banana")
|
|
|
|
assert not root.find("bandanas")
|
|
|
|
assert not root.find("apps")
|
|
|
|
assert root.find("apple")
|
|
|
|
assert root.find("all")
|
|
|
|
root.delete("all")
|
|
|
|
assert not root.find("all")
|
|
|
|
root.delete("banana")
|
|
|
|
assert not root.find("banana")
|
|
|
|
assert root.find("bananas")
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def print_results(msg: str, passes: bool) -> None:
|
|
|
|
print(str(msg), "works!" if passes else "doesn't work :(")
|
|
|
|
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def pytests() -> None:
|
2019-09-13 20:24:25 +00:00
|
|
|
assert test_trie()
|
|
|
|
|
|
|
|
|
2021-10-22 17:21:41 +00:00
|
|
|
def main() -> None:
|
2019-09-13 20:24:25 +00:00
|
|
|
"""
|
|
|
|
>>> pytests()
|
|
|
|
"""
|
|
|
|
print_results("Testing trie functionality", test_trie())
|
|
|
|
|
2018-10-19 12:48:28 +00:00
|
|
|
|
2019-09-13 20:24:25 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|