Trie (Prefix Tree)
A trie (also called prefix tree) is a tree-like data structure used to store a dynamic set of strings, where the keys are usually strings. It's particularly efficient for operations involving prefixes, such as autocomplete functionality, spell checkers, and IP routing tables.
Features
- Efficient string storage - shared prefixes reduce memory usage
- Fast prefix operations - check if any word starts with a prefix
- Autocomplete functionality - get suggestions for partial words
- Word management - insert, search, and delete words efficiently
- Case sensitivity control - support both case-sensitive and case-insensitive modes
- Advanced operations - longest common prefix, shortest unique prefix
- Memory efficient - common prefixes are stored only once
- Flexible traversal - get all words or words with specific prefix
Time Complexities
| Operation | Time Complexity | Description |
|---|---|---|
| Insert | O(m) | Insert word of length m |
| Search | O(m) | Search for word of length m |
| Delete | O(m) | Delete word of length m |
| Prefix Check | O(p) | Check if prefix of length p exists |
| Get Words with Prefix | O(p + n·k) | p = prefix length, n = results, k = avg word length |
| Autocomplete | O(p + n·k) | Get suggestions for prefix |
| Longest Common Prefix | O(n·m) | n = number of words, m = avg length |
| Count with Prefix | O(p + n·k) | Count words starting with prefix |
Space Complexity: O(ALPHABET_SIZE × N × M) where N is number of words and M is average length
Basic Usage
Creating and Initializing
from algo.data_structs.strings.trie import Trie, TrieNode
# Create empty trie (case-sensitive by default)
trie = Trie()
print(trie.is_empty()) # True
print(trie.size()) # 0
print(len(trie)) # 0
print(trie.case_sensitive) # True
# Create case-insensitive trie
trie_ci = Trie(case_sensitive=False)
print(trie_ci.case_sensitive) # False
# Check initial state
print(str(trie)) # Empty Trie
print(repr(trie)) # Trie(size=0, case_sensitive=True)
Creating Nodes
# Create individual trie nodes
root = TrieNode()
print(root.char) # None (root has no character)
print(root.is_end) # False
print(root.has_children()) # False
# Create character node
node = TrieNode('a', is_end=True)
print(node.char) # 'a'
print(node.is_end) # True
print(str(node)) # TrieNode('a', end=True)
# Add children to node
child_b = node.add_child('b')
child_c = node.add_child('c')
print(node.has_children()) # True
print(node.get_children_chars()) # ['b', 'c']
Basic Word Operations
# Insert words
trie = Trie()
trie.insert("cat")
trie.insert("car")
trie.insert("card")
print(trie.size()) # 3
print(trie.is_empty()) # False
print(len(trie)) # 3
# Search for words
print(trie.search("cat")) # True
print(trie.search("car")) # True
print(trie.search("ca")) # False (not a complete word)
print(trie.search("cards")) # False
# Using 'in' operator
print("cat" in trie) # True
print("dog" in trie) # False
Word Insertion
Single Word Insertion
trie = Trie()
# Insert first word
trie.insert("hello")
print(f"Size: {trie.size()}") # Size: 1
print(f"Contains 'hello': {trie.search('hello')}") # True
print(f"Contains 'hell': {trie.search('hell')}") # False
# Insert related word
trie.insert("help")
print(f"Size: {trie.size()}") # Size: 2
print(f"Words: {trie.get_all_words()}") # ['hello', 'help']
Multiple Word Insertion
trie = Trie()
words = ["apple", "app", "application", "apply", "appreciate"]
# Insert all words
for word in words:
trie.insert(word)
print(f"Total words: {trie.size()}") # Total words: 5
# Verify all words were inserted
for word in words:
assert trie.search(word), f"Word '{word}' not found"
print("All words successfully inserted!")
Handling Duplicates
trie = Trie()
# Insert same word multiple times
trie.insert("test")
print(f"Size after first insert: {trie.size()}") # 1
trie.insert("test") # Duplicate - should not increase size
print(f"Size after duplicate: {trie.size()}") # 1
trie.insert("testing") # Different word
print(f"Size after new word: {trie.size()}") # 2
Prefix Relationships
trie = Trie()
# Insert words where one is prefix of another
trie.insert("car")
trie.insert("card")
trie.insert("cards")
trie.insert("care")
trie.insert("careful")
print(f"All words: {trie.get_all_words()}")
# ['car', 'card', 'cards', 'care', 'careful']
# All are separate words even though some are prefixes
print(f"'car' exists: {trie.search('car')}") # True
print(f"'card' exists: {trie.search('card')}") # True
print(f"'care' exists: {trie.search('care')}") # True
Case Sensitivity
Case-Sensitive Mode (Default)
# Case-sensitive trie treats different cases as different words
trie_cs = Trie(case_sensitive=True)
trie_cs.insert("Hello")
trie_cs.insert("hello")
trie_cs.insert("HELLO")
print(f"Size: {trie_cs.size()}") # 3 (all different)
print(f"'Hello': {trie_cs.search('Hello')}") # True
print(f"'hello': {trie_cs.search('hello')}") # True
print(f"'HELLO': {trie_cs.search('HELLO')}") # True
print(f"'hElLo': {trie_cs.search('hElLo')}") # False
Case-Insensitive Mode
# Case-insensitive trie normalizes to lowercase
trie_ci = Trie(case_sensitive=False)
trie_ci.insert("Hello")
trie_ci.insert("hello")
trie_ci.insert("HELLO")
trie_ci.insert("hElLo")
print(f"Size: {trie_ci.size()}") # 1 (all same word)
print(f"'Hello': {trie_ci.search('Hello')}") # True
print(f"'hello': {trie_ci.search('hello')}") # True
print(f"'HELLO': {trie_ci.search('HELLO')}") # True
print(f"'hElLo': {trie_ci.search('hElLo')}") # True
Handling Empty Strings
trie = Trie()
# Empty strings are ignored
trie.insert("")
print(f"Size after empty insert: {trie.size()}") # 0
print(f"Empty string search: {trie.search('')}") # False
# Insert valid word
trie.insert("word")
print(f"Size after valid insert: {trie.size()}") # 1
Prefix Operations
Checking Prefixes
trie = Trie()
words = ["cat", "car", "card", "care", "dog", "door", "dodge"]
for word in words:
trie.insert(word)
# Check if any word starts with prefix
print(f"Starts with 'ca': {trie.starts_with('ca')}") # True
print(f"Starts with 'car': {trie.starts_with('car')}") # True
print(f"Starts with 'do': {trie.starts_with('do')}") # True
print(f"Starts with 'bat': {trie.starts_with('bat')}") # False
# Empty prefix check
print(f"Starts with '': {trie.starts_with('')}") # True (if trie not empty)
Getting Words with Prefix
# Get all words that start with a specific prefix
car_words = trie.get_words_with_prefix("car")
print(f"Words starting with 'car': {car_words}")
# ['car', 'card', 'care']
do_words = trie.get_words_with_prefix("do")
print(f"Words starting with 'do': {do_words}")
# ['dog', 'dodge', 'door']
# Non-existent prefix
xyz_words = trie.get_words_with_prefix("xyz")
print(f"Words starting with 'xyz': {xyz_words}")
# []
# Single character prefix
c_words = trie.get_words_with_prefix("c")
print(f"Words starting with 'c': {c_words}")
# ['car', 'card', 'care', 'cat']
Counting Words with Prefix
# Count words without retrieving them
print(f"Count 'car' prefix: {trie.count_words_with_prefix('car')}") # 3
print(f"Count 'ca' prefix: {trie.count_words_with_prefix('ca')}") # 4
print(f"Count 'do' prefix: {trie.count_words_with_prefix('do')}") # 3
print(f"Count 'z' prefix: {trie.count_words_with_prefix('z')}") # 0
# More efficient than len(get_words_with_prefix()) for large results
Getting All Words
# Retrieve all words in the trie
all_words = trie.get_all_words()
print(f"All words: {all_words}")
# ['car', 'card', 'care', 'cat', 'dog', 'dodge', 'door']
# Words are returned in alphabetical order
print(f"Total count: {len(all_words)}") # 7
print(f"Same as size: {len(all_words) == trie.size()}") # True
Autocomplete Functionality
Basic Autocomplete
trie = Trie()
dictionary = [
"apple", "application", "apply", "appreciate", "approach",
"banana", "band", "bandana", "bank",
"cat", "car", "card", "care", "careful"
]
for word in dictionary:
trie.insert(word)
# Get autocomplete suggestions
suggestions = trie.autocomplete("app")
print(f"Suggestions for 'app': {suggestions}")
# ['apple', 'application', 'apply', 'appreciate', 'approach']
suggestions = trie.autocomplete("ban")
print(f"Suggestions for 'ban': {suggestions}")
# ['banana', 'band', 'bandana', 'bank']
Limited Autocomplete
# Limit number of suggestions
limited_suggestions = trie.autocomplete("app", max_suggestions=3)
print(f"Limited suggestions for 'app': {limited_suggestions}")
# ['apple', 'application', 'apply']
# Very restrictive limit
single_suggestion = trie.autocomplete("car", max_suggestions=1)
print(f"Single suggestion for 'car': {single_suggestion}")
# ['car']
# No suggestions for non-existent prefix
no_suggestions = trie.autocomplete("xyz", max_suggestions=5)
print(f"Suggestions for 'xyz': {no_suggestions}")
# []
Real-world Autocomplete Example
def build_search_autocomplete():
"""Build a search autocomplete system."""
search_trie = Trie(case_sensitive=False)
# Common search terms
search_terms = [
"python programming", "python tutorial", "python basics",
"java programming", "java tutorial", "java basics",
"machine learning", "machine learning basics", "machine learning tutorial",
"data science", "data structures", "data analysis",
"web development", "web design", "website creation"
]
# Build the trie
for term in search_terms:
search_trie.insert(term)
return search_trie
# Use the autocomplete system
search_engine = build_search_autocomplete()
def get_search_suggestions(query, max_results=5):
"""Get search suggestions for a query."""
return search_engine.autocomplete(query.lower(), max_results)
# Test the search system
print("Search suggestions for 'python':")
for suggestion in get_search_suggestions("python"):
print(f" - {suggestion}")
print("\nSearch suggestions for 'machine':")
for suggestion in get_search_suggestions("machine", 3):
print(f" - {suggestion}")
Word Deletion
Basic Deletion
trie = Trie()
words = ["cat", "car", "card", "care", "dog"]
for word in words:
trie.insert(word)
print(f"Initial size: {trie.size()}") # 5
# Delete existing word
success = trie.delete("card")
print(f"Deleted 'card': {success}") # True
print(f"Size after deletion: {trie.size()}") # 4
print(f"'card' exists: {trie.search('card')}") # False
print(f"'car' exists: {trie.search('car')}") # True (not affected)
Deleting Prefix Words
# When deleting a word that is prefix of others
trie = Trie()
trie.insert("car")
trie.insert("card")
trie.insert("cards")
print(f"Before deletion: {trie.get_all_words()}") # ['car', 'card', 'cards']
# Delete the prefix word
trie.delete("car")
print(f"After deleting 'car': {trie.get_all_words()}") # ['card', 'cards']
print(f"'car' exists: {trie.search('car')}") # False
print(f"'card' exists: {trie.search('card')}") # True
print(f"'cards' exists: {trie.search('cards')}") # True
Deleting Non-existent Words
trie = Trie()
trie.insert("hello")
# Try to delete words that don't exist
print(f"Delete 'world': {trie.delete('world')}") # False
print(f"Delete 'hell': {trie.delete('hell')}") # False (prefix, not word)
print(f"Delete 'hellos': {trie.delete('hellos')}") # False
# Size remains unchanged
print(f"Size: {trie.size()}") # 1
print(f"'hello' still exists: {trie.search('hello')}") # True
Deleting Last Word
trie = Trie()
trie.insert("onlyword")
print(f"Before deletion - Empty: {trie.is_empty()}") # False
# Delete the only word
success = trie.delete("onlyword")
print(f"Deletion successful: {success}") # True
print(f"After deletion - Empty: {trie.is_empty()}") # True
print(f"Size: {trie.size()}") # 0
Advanced Operations
Longest Common Prefix
# Find longest common prefix of all words
trie1 = Trie()
words1 = ["flower", "flow", "flight"]
for word in words1:
trie1.insert(word)
lcp1 = trie1.get_longest_common_prefix()
print(f"LCP of {words1}: '{lcp1}'") # 'fl'
# Trie with no common prefix
trie2 = Trie()
words2 = ["dog", "racecar", "car"]
for word in words2:
trie2.insert(word)
lcp2 = trie2.get_longest_common_prefix()
print(f"LCP of {words2}: '{lcp2}'") # ''
# Single word trie
trie3 = Trie()
trie3.insert("hello")
lcp3 = trie3.get_longest_common_prefix()
print(f"LCP of single word: '{lcp3}'") # 'hello'
# Empty trie
trie4 = Trie()
lcp4 = trie4.get_longest_common_prefix()
print(f"LCP of empty trie: '{lcp4}'") # ''
Shortest Unique Prefix
trie = Trie()
words = ["cat", "car", "card", "care", "dog", "door"]
for word in words:
trie.insert(word)
# Find shortest unique prefix for each word
for word in words:
prefix = trie.get_shortest_unique_prefix(word)
print(f"Shortest unique prefix of '{word}': '{prefix}'")
# Output:
# Shortest unique prefix of 'cat': 'cat'
# Shortest unique prefix of 'car': 'car'
# Shortest unique prefix of 'card': 'card'
# Shortest unique prefix of 'care': 'care'
# Shortest unique prefix of 'dog': 'd'
# Shortest unique prefix of 'door': 'do'
# Non-existent word
prefix = trie.get_shortest_unique_prefix("elephant")
print(f"Prefix for non-existent word: {prefix}") # None
Contains Prefix Check
trie = Trie()
trie.insert("car")
trie.insert("card")
trie.insert("care")
# Check if trie contains any prefix of given words
print(f"Contains prefix of 'careful': {trie.contains_prefix('careful')}") # True ('care')
print(f"Contains prefix of 'cards': {trie.contains_prefix('cards')}") # True ('card')
print(f"Contains prefix of 'car': {trie.contains_prefix('car')}") # True (exact match)
print(f"Contains prefix of 'cat': {trie.contains_prefix('cat')}") # False
print(f"Contains prefix of 'dog': {trie.contains_prefix('dog')}") # False
Iteration and Traversal
Iterating Over Words
trie = Trie()
words = ["apple", "banana", "cherry", "date"]
for word in words:
trie.insert(word)
# Direct iteration
print("Words in trie:")
for word in trie:
print(f" - {word}")
# Using list comprehension
word_lengths = [len(word) for word in trie]
print(f"Word lengths: {word_lengths}")
# Convert to list
all_words = list(trie)
print(f"All words: {all_words}")
Using Built-in Functions
# len() function
print(f"Number of words: {len(trie)}")
# 'in' operator for membership testing
test_words = ["apple", "grape", "banana", "kiwi"]
for word in test_words:
if word in trie:
print(f"'{word}' is in the trie")
else:
print(f"'{word}' is NOT in the trie")
# bool() function (True if not empty)
print(f"Trie is not empty: {bool(trie)}")
empty_trie = Trie()
print(f"Empty trie is empty: {not bool(empty_trie)}")
String Representation and Debugging
String Representations
# Empty trie
empty_trie = Trie()
print(f"Empty trie: {str(empty_trie)}") # Empty Trie
print(f"Empty repr: {repr(empty_trie)}") # Trie(size=0, case_sensitive=True)
# Trie with few words
small_trie = Trie()
small_trie.insert("cat")
small_trie.insert("car")
print(f"Small trie: {str(small_trie)}") # Trie(2 words): ['car', 'cat']
# Trie with many words (truncated display)
large_trie = Trie()
words = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape"]
for word in words:
large_trie.insert(word)
print(f"Large trie: {str(large_trie)}") # Trie(7 words): ['apple', 'banana', 'cherry', 'date', 'elderberry']...
# Detailed representation
print(f"Detailed repr: {repr(large_trie)}") # Trie(size=7, case_sensitive=True)
Node Representations
# Individual node representations
root = TrieNode()
print(f"Root node: {str(root)}") # TrieNode('None', end=False)
print(f"Root repr: {repr(root)}") # TrieNode(char='None', is_end=False, children=[])
char_node = TrieNode('a', is_end=True)
char_node.add_child('b')
char_node.add_child('c')
print(f"Char node: {str(char_node)}") # TrieNode('a', end=True)
print(f"Char repr: {repr(char_node)}") # TrieNode(char='a', is_end=True, children=['b', 'c'])
Error Handling and Edge Cases
Empty Trie Operations
empty_trie = Trie()
# Safe operations on empty trie
print(f"Empty trie size: {empty_trie.size()}") # 0
print(f"Empty trie is empty: {empty_trie.is_empty()}") # True
print(f"Search in empty: {empty_trie.search('anything')}") # False
print(f"Starts with in empty: {empty_trie.starts_with('any')}") # False
print(f"Delete from empty: {empty_trie.delete('anything')}") # False
print(f"Get all words: {empty_trie.get_all_words()}") # []
print(f"Words with prefix: {empty_trie.get_words_with_prefix('a')}") # []
print(f"Autocomplete: {empty_trie.autocomplete('a')}") # []
print(f"LCP: '{empty_trie.get_longest_common_prefix()}'") # ''
Special Characters and Unicode
# Trie with special characters
special_trie = Trie()
special_words = ["hello!", "world?", "test@domain.com", "user#123"]
for word in special_words:
special_trie.insert(word)
print(f"Special words: {special_trie.get_all_words()}")
print(f"Search 'hello!': {special_trie.search('hello!')}") # True
print(f"Prefix 'test@': {special_trie.starts_with('test@')}") # True
# Unicode characters
unicode_trie = Trie()
unicode_words = ["café", "naïve", "résumé", "piñata", "αβγ"]
for word in unicode_words:
unicode_trie.insert(word)
print(f"Unicode words: {unicode_trie.get_all_words()}")
print(f"Search 'café': {unicode_trie.search('café')}") # True
print(f"Prefix 'rés': {unicode_trie.starts_with('rés')}") # True
Large Data Handling
# Handling large datasets
import time
def benchmark_trie_operations():
"""Benchmark trie operations with large dataset."""
trie = Trie()
# Generate test data
import string
import random
words = []
for i in range(10000):
length = random.randint(3, 15)
word = ''.join(random.choices(string.ascii_lowercase, k=length))
words.append(word)
# Benchmark insertion
start_time = time.time()
for word in words:
trie.insert(word)
insert_time = time.time() - start_time
print(f"Inserted {len(words)} words in {insert_time:.2f} seconds")
print(f"Final trie size: {trie.size()}")
# Benchmark search
start_time = time.time()
found_count = sum(1 for word in words[:1000] if trie.search(word))
search_time = time.time() - start_time
print(f"Searched 1000 words in {search_time:.4f} seconds")
print(f"Found {found_count} words")
# Uncomment to run benchmark
# benchmark_trie_operations()
Practical Applications
Spell Checker Implementation
class SimpleSpellChecker:
"""A simple spell checker using trie."""
def __init__(self):
self.dictionary = Trie(case_sensitive=False)
self._load_dictionary()
def _load_dictionary(self):
"""Load common English words."""
common_words = [
"the", "be", "to", "of", "and", "a", "in", "that", "have",
"i", "it", "for", "not", "on", "with", "he", "as", "you",
"do", "at", "this", "but", "his", "by", "from", "they",
"we", "say", "her", "she", "or", "an", "will", "my",
"one", "all", "would", "there", "their", "what", "so",
"up", "out", "if", "about", "who", "get", "which", "go",
"me", "when", "make", "can", "like", "time", "no", "just",
"him", "know", "take", "people", "into", "year", "your",
"good", "some", "could", "them", "see", "other", "than",
"then", "now", "look", "only", "come", "its", "over",
"think", "also", "back", "after", "use", "two", "how",
"our", "work", "first", "well", "way", "even", "new",
"want", "because", "any", "these", "give", "day", "most", "us"
]
for word in common_words:
self.dictionary.insert(word)
def is_spelled_correctly(self, word: str) -> bool:
"""Check if a word is spelled correctly."""
return self.dictionary.search(word)
def get_suggestions(self, word: str, max_suggestions: int = 5) -> List[str]:
"""Get spelling suggestions for a word."""
if self.is_spelled_correctly(word):
return [word] # Word is correct
# Try different prefix lengths
suggestions = []
for i in range(1, len(word) + 1):
prefix = word[:i]
prefix_suggestions = self.dictionary.autocomplete(prefix, max_suggestions)
suggestions.extend(prefix_suggestions)
if len(suggestions) >= max_suggestions:
break
# Remove duplicates and limit results
return list(dict.fromkeys(suggestions))[:max_suggestions]
def check_text(self, text: str) -> dict:
"""Check spelling of all words in text."""
words = text.lower().split()
results = {
'correct': [],
'incorrect': [],
'suggestions': {}
}
for word in words:
# Remove punctuation
clean_word = ''.join(c for c in word if c.isalpha())
if not clean_word:
continue
if self.is_spelled_correctly(clean_word):
results['correct'].append(clean_word)
else:
results['incorrect'].append(clean_word)
results['suggestions'][clean_word] = self.get_suggestions(clean_word)
return results
# Usage example
spell_checker = SimpleSpellChecker()
# Test individual words
print(f"'hello' correct: {spell_checker.is_spelled_correctly('hello')}") # True
print(f"'helo' correct: {spell_checker.is_spelled_correctly('helo')}") # False
# Get suggestions
suggestions = spell_checker.get_suggestions('helo')
print(f"Suggestions for 'helo': {suggestions}")
# Check entire text
text = "The qick brown fox jumps ovr the lazy dog"
results = spell_checker.check_text(text)
print(f"Correct words: {results['correct']}")
print(f"Incorrect words: {results['incorrect']}")
for word, suggestions in results['suggestions'].items():
print(f" '{word}' -> {suggestions}")
Domain Name Router
class DomainRouter:
"""Route requests based on domain patterns using trie."""
def __init__(self):
self.routes = Trie(case_sensitive=False)
self.handlers = {}
def add_route(self, domain_pattern: str, handler: str):
"""Add a domain route."""
# Reverse domain for prefix matching (com.example.api -> api.example.com)
reversed_domain = '.'.join(reversed(domain_pattern.split('.')))
self.routes.insert(reversed_domain)
self.handlers[reversed_domain] = handler
def route_request(self, domain: str) -> str:
"""Find handler for domain."""
reversed_domain = '.'.join(reversed(domain.split('.')))
# Try exact match first
if self.routes.search(reversed_domain):
return self.handlers[reversed_domain]
# Try prefix matching
for route in self.routes.get_all_words():
if reversed_domain.startswith(route):
return self.handlers[route]
return "default_handler"
def get_matching_patterns(self, domain: str) -> List[str]:
"""Get all patterns that could match the domain."""
reversed_domain = '.'.join(reversed(domain.split('.')))
matches = []
for route in self.routes.get_all_words():
if reversed_domain.startswith(route) or route.startswith(reversed_domain):
# Convert back to normal domain format
original_pattern = '.'.join(reversed(route.split('.')))
matches.append(original_pattern)
return matches
# Usage example
router = DomainRouter()
# Add routes
router.add_route("api.example.com", "api_handler")
router.add_route("admin.example.com", "admin_handler")
router.add_route("example.com", "main_handler")
router.add_route("subdomain.api.example.com", "subdomain_api_handler")
# Route requests
test_domains = [
"api.example.com",
"admin.example.com",
"www.example.com",
"subdomain.api.example.com",
"unknown.domain.com"
]
print("Domain routing results:")
for domain in test_domains:
handler = router.route_request(domain)
matches = router.get_matching_patterns(domain)
print(f" {domain} -> {handler} (patterns: {matches})")
IP Address Prefix Matching
class IPPrefixRouter:
"""Route IP addresses using longest prefix matching with trie."""
def __init__(self):
self.prefix_trie = Trie()
self.routes = {}
def _ip_to_binary(self, ip: str) -> str:
"""Convert IP address to binary string."""
parts = ip.split('.')
binary = ''
for part in parts:
binary += format(int(part), '08b')
return binary
def add_route(self, network: str, next_hop: str):
"""Add a network route (e.g., '192.168.1.0/24')."""
ip, prefix_len = network.split('/')
binary_ip = self._ip_to_binary(ip)
prefix = binary_ip[:int(prefix_len)]
self.prefix_trie.insert(prefix)
self.routes[prefix] = {
'network': network,
'next_hop': next_hop,
'prefix_length': int(prefix_len)
}
def route_ip(self, ip: str) -> dict:
"""Find best route for IP address (longest prefix match)."""
binary_ip = self._ip_to_binary(ip)
best_match = None
longest_prefix = ""
# Find longest matching prefix
for route_prefix in self.prefix_trie.get_all_words():
if binary_ip.startswith(route_prefix):
if len(route_prefix) > len(longest_prefix):
longest_prefix = route_prefix
best_match = self.routes[route_prefix]
return best_match or {'network': 'default', 'next_hop': 'default_gateway', 'prefix_length': 0}
def get_routing_table(self) -> List[dict]:
"""Get all routes sorted by prefix length."""
routes = list(self.routes.values())
return sorted(routes, key=lambda x: x['prefix_length'], reverse=True)
# Usage example
ip_router = IPPrefixRouter()
# Add routes
ip_router.add_route("192.168.0.0/16", "gateway1")
ip_router.add_route("192.168.1.0/24", "gateway2")
ip_router.add_route("192.168.1.128/25", "gateway3")
ip_router.add_route("10.0.0.0/8", "gateway4")
# Test routing
test_ips = [
"192.168.1.100",
"192.168.1.200",
"192.168.2.100",
"10.0.1.1",
"8.8.8.8"
]
print("IP routing results:")
for ip in test_ips:
route = ip_router.route_ip(ip)
print(f" {ip} -> {route['next_hop']} (network: {route['network']})")
print(f"\nRouting table:")
for route in ip_router.get_routing_table():
print(f" {route['network']} -> {route['next_hop']}")
Performance Considerations
Memory Usage
def analyze_trie_memory():
"""Analyze memory usage patterns of trie."""
import sys
# Create tries with different characteristics
sparse_trie = Trie() # Few words, little sharing
dense_trie = Trie() # Many words, lots of sharing
# Sparse trie - completely different words
sparse_words = ["apple", "zebra", "moon", "fire", "water"]
for word in sparse_words:
sparse_trie.insert(word)
# Dense trie - many shared prefixes
dense_words = []
prefixes = ["pro", "pre", "sub", "super", "anti"]
suffixes = ["gram", "fix", "marine", "sonic", "body"]
for prefix in prefixes:
for suffix in suffixes:
dense_words.append(prefix + suffix)
for word in dense_words:
dense_trie.insert(word)
print(f"Sparse trie: {len(sparse_words)} words")
print(f"Dense trie: {len(dense_words)} words")
print(f"Memory efficiency: Dense trie has {len(dense_words)/len(sparse_words):.1f}x more words")
# Show prefix sharing benefits
print(f"\nPrefix sharing in dense trie:")
for prefix in ["pro", "pre", "sub"]:
count = dense_trie.count_words_with_prefix(prefix)
print(f" '{prefix}' prefix: {count} words")
# analyze_trie_memory()
Best Practices
def trie_best_practices():
"""Demonstrate best practices for trie usage."""
# 1. Choose appropriate case sensitivity
# For user input/search: case-insensitive
search_trie = Trie(case_sensitive=False)
# For exact string matching: case-sensitive
code_trie = Trie(case_sensitive=True)
# 2. Batch operations for better performance
words_to_insert = ["word1", "word2", "word3", "word4"]
# Good: batch insert
for word in words_to_insert:
search_trie.insert(word)
# 3. Use specific methods for specific needs
# For existence check: use search()
exists = search_trie.search("word1")
# For prefix check: use starts_with()
has_prefix = search_trie.starts_with("wor")
# For counting: use count_words_with_prefix()
count = search_trie.count_words_with_prefix("wor")
# For limited results: use autocomplete()
suggestions = search_trie.autocomplete("wor", max_suggestions=5)
# 4. Consider memory vs. functionality trade-offs
print("Best practices applied!")
return {
'search_trie_size': search_trie.size(),
'code_trie_size': code_trie.size(),
'example_exists': exists,
'example_prefix': has_prefix,
'example_count': count,
'example_suggestions': suggestions
}
# Example usage
results = trie_best_practices()
print("Results:", results)
API Reference
Trie (Prefix Tree) Implementation
A trie is a tree-like data structure used to store a dynamic set of strings, where the keys are usually strings. It's particularly efficient for operations involving prefixes, such as autocomplete functionality.
Key Features:
- Word insertion, search, and deletion
- Prefix-based operations (starts_with, get_words_with_prefix)
- Autocomplete functionality
- Memory-efficient storage of strings with common prefixes
- Case-sensitive and case-insensitive modes
Time Complexities:
- Insert: O(m) where m is the length of the word
- Search: O(m) where m is the length of the word
- Delete: O(m) where m is the length of the word
- Prefix operations: O(p + n) where p is prefix length and n is number of results
Space Complexity: O(ALPHABET_SIZE * N * M) where N is number of words and M is average length
Trie(case_sensitive=True)
Bases: TrieInterface
A trie (prefix tree) implementation for efficient string storage and retrieval.
This implementation supports case-sensitive and case-insensitive modes, and provides comprehensive prefix-based operations.
Initialize a trie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
case_sensitive
|
bool
|
Whether the trie should be case-sensitive |
True
|
__contains__(word)
Check if a word is in the trie using 'in' operator.
__iter__()
Iterate over all words in the trie.
__len__()
Return the number of words in the trie.
__repr__()
Return detailed representation for debugging.
__str__()
Return string representation of the trie.
autocomplete(prefix, max_suggestions=10)
Get autocomplete suggestions for a given prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix to get suggestions for |
required |
max_suggestions
|
int
|
Maximum number of suggestions to return |
10
|
Returns:
| Type | Description |
|---|---|
List[str]
|
List of suggested words (up to max_suggestions) |
contains_prefix(word)
Check if the trie contains any prefix of the given word.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
word
|
str
|
Word to check prefixes for |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if any prefix is found, False otherwise |
count_words_with_prefix(prefix)
Count the number of words that start with the given prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix to count words for |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of words with the prefix |
delete(word)
Delete a word from the trie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
word
|
str
|
Word to delete |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if word was deleted, False if not found |
get_all_words()
Get all words stored in the trie.
Returns:
| Type | Description |
|---|---|
List[str]
|
List of all words in the trie |
get_longest_common_prefix()
Get the longest common prefix of all words in the trie.
Returns:
| Type | Description |
|---|---|
str
|
Longest common prefix string |
get_shortest_unique_prefix(word)
Get the shortest unique prefix for a word.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
word
|
str
|
Word to find unique prefix for |
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
Shortest unique prefix, or None if word not in trie |
get_words_with_prefix(prefix)
Get all words that start with the given prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix to search for |
required |
Returns:
| Type | Description |
|---|---|
List[str]
|
List of words starting with the prefix |
insert(word)
Insert a word into the trie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
word
|
str
|
Word to insert |
required |
is_empty()
Check if the trie is empty.
Returns:
| Type | Description |
|---|---|
bool
|
True if trie is empty, False otherwise |
search(word)
Search for a word in the trie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
word
|
str
|
Word to search for |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if word exists, False otherwise |
size()
Get the number of words in the trie.
Returns:
| Type | Description |
|---|---|
int
|
Number of words stored |
starts_with(prefix)
Check if any word in the trie starts with the given prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if any word starts with prefix, False otherwise |
TrieNode(char=None, is_end=False)
Bases: TrieNodeInterface
A node in the trie structure.
Each node represents a character and maintains references to child nodes and a flag indicating if it marks the end of a word.
Initialize a trie node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
char
|
Optional[str]
|
The character this node represents (None for root) |
None
|
is_end
|
bool
|
Whether this node marks the end of a word |
False
|
__repr__()
Return detailed string representation for debugging.
__str__()
Return string representation of the node.
add_child(char)
Add a child node for the given character.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
char
|
str
|
Character for the new child node |
required |
Returns:
| Type | Description |
|---|---|
TrieNode
|
The child node (new or existing) |
get_child(char)
Get the child node for the given character.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
char
|
str
|
Character to look for |
required |
Returns:
| Type | Description |
|---|---|
Optional[TrieNode]
|
Child node if exists, None otherwise |
get_children_chars()
Get list of characters for all child nodes.
Returns:
| Type | Description |
|---|---|
List[str]
|
List of characters that have child nodes |
has_child(char)
Check if a child exists for the given character.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
char
|
str
|
Character to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if child exists, False otherwise |
has_children()
Check if this node has any children.
Returns:
| Type | Description |
|---|---|
bool
|
True if node has children, False otherwise |
remove_child(char)
Remove the child node for the given character.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
char
|
str
|
Character of child to remove |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if child was removed, False if not found |