-
Notifications
You must be signed in to change notification settings - Fork 4
/
implement-trie-prefix-tree.py
156 lines (139 loc) · 5 KB
/
implement-trie-prefix-tree.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 208. Implement Trie (Prefix Tree)
# 🟠 Medium
#
# https://leetcode.com/problems/implement-trie-prefix-tree/
#
# Tags: Hash Table - String - Design - Trie
import timeit
from collections import defaultdict
# 10e4 calls.
# » Trie 0.00228 seconds
# » UseTrieNode 0.00384 seconds
# Create a class that directly implements all the required methods, the
# class internally uses dictionaries to store and find each node
# children, including the root.
#
# Time complexity: O(n) - Where n is the number of characters in the
# input word. All methods have the same complexity, for each character
# in the input, we do a dictionary access in O(1).
# Space complexity: O(m*n) - Where m is the number of words and n the
# average number of characters per word, each character could take space
# in the trie, even though often that will not be the case because
# characters shared between words will only take space once.
#
# Runtime: 144 ms, faster than 96.10%
# Memory Usage: 27.5 MB, less than 92.13%
class Trie:
def __init__(self):
self.root = {}
# O(n) - Where n is the number of characters.
def insert(self, word: str) -> None:
current = self.root
for w in word:
if w not in current:
current[w] = {}
current = current[w]
current["?"] = True
# O(n) - Where n is the number of characters.
def search(self, word: str) -> bool:
current = self.root
for w in word:
if w not in current:
return False
current = current[w]
return current.get("?", False)
# O(n) - Where n is the number of characters.
def startsWith(self, prefix: str) -> bool:
current = self.root
for w in prefix:
if w not in current:
return False
current = current[w]
return True
# Definition for a trie node
class TrieNode:
def __init__(self):
self.children = defaultdict(TrieNode)
self.isWord = False
# Create a class that uses TrieNode to provide the functionality
# required, the Trie class has a root TrieNode. The usage is similar to
# the dictionary implementation but it results in more readable and
# maintainable code.
#
# Time complexity: O(n) - Where n is the number of characters in the
# input word. All methods have the same complexity, for each character
# in the input, we do a dictionary access in O(1).
# Space complexity: O(m*n) - Where m is the number of words and n the
# average number of characters per word, each character could take space
# in the trie, even though often that will not be the case because
# characters shared between words will only take space once.
#
# Runtime: 446 ms, faster than 28.62%
# Memory Usage: 31.9 MB, less than 23.72%
class UseTrieNode:
def __init__(self):
self.root = TrieNode()
# O(n) - Where n is the number of characters.
def insert(self, word: str) -> None:
current = self.root
for w in word:
# Default dict will create the entry if not found.
current = current.children[w]
current.isWord = True
# O(n) - Where n is the number of characters.
def search(self, word: str) -> bool:
current = self.root
for w in word:
if w not in current.children:
return False
current = current.children[w]
return current.isWord
# O(n) - Where n is the number of characters.
def startsWith(self, prefix: str) -> bool:
current = self.root
for w in prefix:
if w not in current.children:
return False
current = current.children[w]
return True
def test():
executors = [
Trie,
UseTrieNode,
]
tests = [
[
[
"Trie",
"insert",
"search",
"search",
"startsWith",
"insert",
"search",
],
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]],
[None, None, True, False, True, None, True],
],
]
for executor in executors:
start = timeit.default_timer()
for _ in range(1):
for col, t in enumerate(tests):
# The constructor does not take any parameters.
sol = executor()
for i in range(1, len(t[0])):
call = t[0][i]
# All the methods take exactly one parameter.
result = getattr(sol, call)(t[1][i][0])
exp = t[2][i]
assert result == exp, (
f"\033[93m» {result} <> {exp}\033[91m for"
+ f" test {col} using \033[1m{executor.__name__}"
)
stop = timeit.default_timer()
used = str(round(stop - start, 5))
cols = "{0:20}{1:10}{2:10}"
res = cols.format(executor.__name__, used, "seconds")
print(f"\033[92m» {res}\033[0m")
test()