Skip to content

Commit 5aad9a7

Browse files
Add Trie data structure implementation
1 parent a98308d commit 5aad9a7

File tree

4 files changed

+232
-2
lines changed

4 files changed

+232
-2
lines changed

pydatastructs/trees/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
m_ary_trees,
66
space_partitioning_trees,
77
heaps,
8-
_extensions
8+
_extensions,
9+
trie # Import the new trie module
910
)
1011

1112
from .binary_trees import (
@@ -24,7 +25,6 @@
2425
from .m_ary_trees import (
2526
MAryTreeNode, MAryTree
2627
)
27-
2828
__all__.extend(m_ary_trees.__all__)
2929

3030
from .space_partitioning_trees import (
@@ -39,3 +39,6 @@
3939
BinomialHeap
4040
)
4141
__all__.extend(heaps.__all__)
42+
43+
from .trie import Trie # Import the Trie class
44+
__all__.append("Trie") # Add Trie to the list of exported classes

pydatastructs/trees/tests/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""
2+
Initialization file for the test package.
3+
Ensures that the test suite can properly import modules from pydatastructs.
4+
"""
5+
6+
import pytest
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from pydatastructs.trees.trie import Trie
3+
4+
def test_trie_insert_and_search():
5+
trie = Trie()
6+
trie.insert("apple")
7+
assert trie.search("apple") is True # Word should be present
8+
assert trie.search("app") is False # Partial word should not be found
9+
assert trie.starts_with("app") is True # Prefix should be detected
10+
11+
def test_trie_multiple_inserts():
12+
trie = Trie()
13+
trie.insert("apple")
14+
trie.insert("app")
15+
assert trie.search("app") is True # Now "app" should be present
16+
assert trie.search("apple") is True
17+
18+
def test_trie_delete():
19+
trie = Trie()
20+
trie.insert("apple")
21+
trie.insert("app")
22+
assert trie.delete("apple") is True # "apple" should be removed
23+
assert trie.search("apple") is False # "apple" should not be found
24+
assert trie.search("app") is True # "app" should still exist
25+
26+
def test_trie_delete_non_existent():
27+
trie = Trie()
28+
assert trie.delete("banana") is False # Deleting a non-existent word should return False
29+
30+
def test_trie_empty_string():
31+
trie = Trie()
32+
assert trie.search("") is False # Searching for an empty string should return False
33+
assert trie.starts_with("") is True # Empty prefix should return True
34+
35+
if __name__ == "__main__":
36+
pytest.main()

pydatastructs/trees/trie.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""
2+
Implementation of Trie (Prefix Tree) data structure.
3+
"""
4+
5+
__all__ = [
6+
'Trie'
7+
]
8+
9+
class TrieNode:
10+
"""
11+
Represents a node in the Trie.
12+
"""
13+
__slots__ = ['children', 'is_end_of_word']
14+
15+
def __init__(self):
16+
"""
17+
Initializes a TrieNode.
18+
"""
19+
self.children = {}
20+
self.is_end_of_word = False
21+
22+
class Trie:
23+
"""
24+
Represents a Trie (Prefix Tree) data structure.
25+
26+
Examples
27+
========
28+
29+
>>> from pydatastructs.trees.trie import Trie
30+
>>> trie = Trie()
31+
>>> trie.insert("apple")
32+
>>> trie.search("apple")
33+
True
34+
>>> trie.search("app")
35+
False
36+
>>> trie.starts_with("app")
37+
True
38+
>>> trie.insert("app")
39+
>>> trie.search("app")
40+
True
41+
>>> trie.delete("apple")
42+
True
43+
>>> trie.search("apple")
44+
False
45+
>>> trie.search("app")
46+
True
47+
"""
48+
__slots__ = ['root']
49+
50+
def __init__(self):
51+
"""
52+
Initializes an empty Trie.
53+
"""
54+
self.root = TrieNode()
55+
56+
def insert(self, word):
57+
"""
58+
Inserts a word into the trie.
59+
60+
Parameters
61+
==========
62+
63+
word: str
64+
The word to insert
65+
66+
Returns
67+
=======
68+
69+
None
70+
"""
71+
node = self.root
72+
for char in word:
73+
if char not in node.children:
74+
node.children[char] = TrieNode()
75+
node = node.children[char]
76+
node.is_end_of_word = True
77+
78+
def search(self, word):
79+
"""
80+
Returns True if the word is in the trie.
81+
82+
Parameters
83+
==========
84+
85+
word: str
86+
The word to search for
87+
88+
Returns
89+
=======
90+
91+
bool
92+
True if the word is in the trie, False otherwise
93+
"""
94+
node = self._get_node(word)
95+
return node is not None and node.is_end_of_word
96+
97+
def starts_with(self, prefix):
98+
"""
99+
Returns True if there is any word in the trie
100+
that starts with the given prefix.
101+
102+
Parameters
103+
==========
104+
105+
prefix: str
106+
The prefix to check
107+
108+
Returns
109+
=======
110+
111+
bool
112+
True if there is any word with the given prefix,
113+
False otherwise
114+
"""
115+
return self._get_node(prefix) is not None
116+
117+
def delete(self, word):
118+
"""
119+
Deletes a word from the trie if it exists.
120+
121+
Parameters
122+
==========
123+
124+
word: str
125+
The word to delete
126+
127+
Returns
128+
=======
129+
130+
bool
131+
True if the word was deleted, False if it wasn't in the trie
132+
"""
133+
def _delete_helper(node, word, depth=0):
134+
# If we've reached the end of the word
135+
if depth == len(word):
136+
# If the word exists in the trie
137+
if node.is_end_of_word:
138+
node.is_end_of_word = False
139+
# Return True if this node can be deleted
140+
# (has no children and is not end of another word)
141+
return len(node.children) == 0
142+
return False
143+
144+
char = word[depth]
145+
if char not in node.children:
146+
return False
147+
148+
should_delete_child = _delete_helper(node.children[char], word, depth + 1)
149+
150+
# If we should delete the child
151+
if should_delete_child:
152+
del node.children[char]
153+
# Return True if this node can be deleted
154+
# (has no children and is not end of another word)
155+
return len(node.children) == 0 and not node.is_end_of_word
156+
157+
return False
158+
159+
if not word:
160+
return False
161+
162+
return True if _delete_helper(self.root, word) else self.search(word)
163+
164+
def _get_node(self, prefix):
165+
"""
166+
Returns the node at the end of the prefix, or None if not found.
167+
168+
Parameters
169+
==========
170+
171+
prefix: str
172+
The prefix to find
173+
174+
Returns
175+
=======
176+
177+
TrieNode or None
178+
The node at the end of the prefix, or None if the prefix doesn't exist
179+
"""
180+
node = self.root
181+
for char in prefix:
182+
if char not in node.children:
183+
return None
184+
node = node.children[char]
185+
return node

0 commit comments

Comments
 (0)