-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement trie algorithm with insert, search, starts_with, and delete…
… methods; add comprehensive test suite
- Loading branch information
1 parent
1ee7c81
commit fbd1ea0
Showing
3 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import unittest | ||
from trie import Trie | ||
|
||
class TestTrie(unittest.TestCase): | ||
def setUp(self): | ||
self.trie = Trie() | ||
|
||
def test_insert_and_search(self): | ||
self.trie.insert("apple") | ||
self.assertTrue(self.trie.search("apple")) | ||
self.assertFalse(self.trie.search("app")) | ||
self.assertFalse(self.trie.search("apples")) | ||
|
||
def test_starts_with(self): | ||
self.trie.insert("apple") | ||
self.assertTrue(self.trie.starts_with("app")) | ||
self.assertTrue(self.trie.starts_with("apple")) | ||
self.assertFalse(self.trie.starts_with("apq")) | ||
|
||
def test_delete(self): | ||
self.trie.insert("apple") | ||
self.trie.insert("app") | ||
self.assertTrue(self.trie.delete("apple")) | ||
self.assertFalse(self.trie.search("apple")) | ||
self.assertTrue(self.trie.search("app")) | ||
self.assertFalse(self.trie.delete("apple")) | ||
|
||
def test_empty_string(self): | ||
self.trie.insert("") | ||
self.assertTrue(self.trie.search("")) | ||
self.assertTrue(self.trie.starts_with("")) | ||
self.assertTrue(self.trie.delete("")) | ||
|
||
def test_case_sensitivity(self): | ||
self.trie.insert("Apple") | ||
self.assertTrue(self.trie.search("Apple")) | ||
self.assertFalse(self.trie.search("apple")) | ||
|
||
def test_non_alphabetic_characters(self): | ||
self.trie.insert("hello123") | ||
self.assertTrue(self.trie.search("hello123")) | ||
self.assertFalse(self.trie.search("hello")) | ||
|
||
def test_overlapping_prefixes(self): | ||
self.trie.insert("car") | ||
self.trie.insert("carpet") | ||
self.assertTrue(self.trie.search("car")) | ||
self.assertTrue(self.trie.search("carpet")) | ||
self.assertTrue(self.trie.starts_with("car")) | ||
|
||
def test_delete_prefix(self): | ||
self.trie.insert("car") | ||
self.trie.insert("carpet") | ||
self.assertTrue(self.trie.delete("car")) | ||
self.assertFalse(self.trie.search("car")) | ||
self.assertTrue(self.trie.search("carpet")) | ||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from typing import Union | ||
|
||
class TrieNode: | ||
def __init__(self): | ||
self.children = {} | ||
self.is_end_of_word = False | ||
|
||
class Trie: | ||
def __init__(self): | ||
self.root = TrieNode() | ||
|
||
def insert(self, word: str) -> None: | ||
node = self.root | ||
for char in word: | ||
if char not in node.children: | ||
node.children[char] = TrieNode() | ||
node = node.children[char] | ||
node.is_end_of_word = True | ||
|
||
def search(self, word: str) -> bool: | ||
node = self._traverse(word) | ||
return node is not None and node.is_end_of_word | ||
|
||
def starts_with(self, prefix: str) -> bool: | ||
return self._traverse(prefix) is not None | ||
|
||
def delete(self, word: str) -> bool: | ||
def _delete_helper(node, word, index): | ||
if index == len(word): | ||
if not node.is_end_of_word: | ||
return False | ||
node.is_end_of_word = False | ||
return len(node.children) == 0 | ||
|
||
char = word[index] | ||
if char not in node.children: | ||
return False | ||
|
||
should_delete_child = _delete_helper(node.children[char], word, index + 1) | ||
|
||
if should_delete_child: | ||
del node.children[char] | ||
return len(node.children) == 0 and not node.is_end_of_word | ||
return False | ||
|
||
if not word: | ||
if self.root.is_end_of_word: | ||
self.root.is_end_of_word = False | ||
return True | ||
return False | ||
|
||
return _delete_helper(self.root, word, 0) | ||
|
||
def _traverse(self, word: str) -> Union[TrieNode, None]: | ||
node = self.root | ||
for char in word: | ||
if char not in node.children: | ||
return None | ||
node = node.children[char] | ||
return node |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Trie Algorithm Implementation Plan | ||
|
||
## 1. Structure | ||
- Create a `TrieNode` class: | ||
- `children`: A dictionary to store child nodes (key: character, value: TrieNode) | ||
- `is_end_of_word`: A boolean to mark the end of a word | ||
|
||
- Create a `Trie` class: | ||
- `root`: A TrieNode instance to serve as the root of the trie | ||
|
||
## 2. Methods | ||
### Trie class methods: | ||
a. `insert(word: str) -> None`: | ||
- Insert a word into the trie | ||
- Time complexity: O(m), where m is the length of the word | ||
|
||
b. `search(word: str) -> bool`: | ||
- Search for a word in the trie | ||
- Return True if the word exists, False otherwise | ||
- Time complexity: O(m), where m is the length of the word | ||
|
||
c. `starts_with(prefix: str) -> bool`: | ||
- Check if there is any word in the trie that starts with the given prefix | ||
- Return True if such a word exists, False otherwise | ||
- Time complexity: O(m), where m is the length of the prefix | ||
|
||
d. `delete(word: str) -> bool`: | ||
- Remove a word from the trie if it exists | ||
- Return True if the word was deleted, False if it wasn't found | ||
- Time complexity: O(m), where m is the length of the word | ||
|
||
## 3. Edge Cases to Consider | ||
- Empty string input | ||
- Case sensitivity (decide whether to handle case-insensitive operations) | ||
- Non-alphabetic characters | ||
- Overlapping prefixes | ||
- Deleting a word that is a prefix of another word | ||
|
||
## 4. Testing | ||
- Create a separate test file to verify the functionality of the Trie class | ||
- Test cases should include: | ||
- Inserting and searching for words | ||
- Searching for non-existent words | ||
- Checking prefixes | ||
- Deleting words (including words that are prefixes of other words) | ||
- Edge cases mentioned above | ||
|
||
## 5. Implementation Steps | ||
1. Create the `TrieNode` class | ||
2. Implement the `Trie` class with its constructor | ||
3. Implement the `insert` method | ||
4. Implement the `search` method | ||
5. Implement the `starts_with` method | ||
6. Implement the `delete` method | ||
7. Create a test file and write comprehensive tests | ||
8. Run tests and debug if necessary | ||
|
||
## 6. Time and Space Complexity Analysis | ||
- Time Complexity: | ||
- Insertion: O(m) where m is the length of the word | ||
- Search: O(m) where m is the length of the word | ||
- Prefix search: O(m) where m is the length of the prefix | ||
- Deletion: O(m) where m is the length of the word | ||
|
||
- Space Complexity: | ||
- O(n * m) where n is the number of words and m is the average length of the words | ||
|
||
This implementation will provide an efficient solution for prefix-based word storage and retrieval, which is the primary use case for a trie data structure. |