exonware-xwnode 0.0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exonware/__init__.py +14 -0
- exonware/xwnode/__init__.py +127 -0
- exonware/xwnode/base.py +676 -0
- exonware/xwnode/config.py +178 -0
- exonware/xwnode/contracts.py +730 -0
- exonware/xwnode/errors.py +503 -0
- exonware/xwnode/facade.py +460 -0
- exonware/xwnode/strategies/__init__.py +158 -0
- exonware/xwnode/strategies/advisor.py +463 -0
- exonware/xwnode/strategies/edges/__init__.py +32 -0
- exonware/xwnode/strategies/edges/adj_list.py +227 -0
- exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
- exonware/xwnode/strategies/edges/base.py +169 -0
- exonware/xwnode/strategies/flyweight.py +328 -0
- exonware/xwnode/strategies/impls/__init__.py +13 -0
- exonware/xwnode/strategies/impls/_base_edge.py +403 -0
- exonware/xwnode/strategies/impls/_base_node.py +307 -0
- exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
- exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
- exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
- exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
- exonware/xwnode/strategies/impls/edge_coo.py +533 -0
- exonware/xwnode/strategies/impls/edge_csc.py +447 -0
- exonware/xwnode/strategies/impls/edge_csr.py +492 -0
- exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
- exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
- exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
- exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
- exonware/xwnode/strategies/impls/edge_octree.py +574 -0
- exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
- exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
- exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
- exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
- exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
- exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
- exonware/xwnode/strategies/manager.py +775 -0
- exonware/xwnode/strategies/metrics.py +538 -0
- exonware/xwnode/strategies/migration.py +432 -0
- exonware/xwnode/strategies/nodes/__init__.py +50 -0
- exonware/xwnode/strategies/nodes/_base_node.py +307 -0
- exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
- exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
- exonware/xwnode/strategies/nodes/array_list.py +209 -0
- exonware/xwnode/strategies/nodes/base.py +247 -0
- exonware/xwnode/strategies/nodes/deque.py +200 -0
- exonware/xwnode/strategies/nodes/hash_map.py +135 -0
- exonware/xwnode/strategies/nodes/heap.py +307 -0
- exonware/xwnode/strategies/nodes/linked_list.py +232 -0
- exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
- exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
- exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
- exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
- exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
- exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
- exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
- exonware/xwnode/strategies/nodes/node_btree.py +357 -0
- exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
- exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
- exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
- exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
- exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
- exonware/xwnode/strategies/nodes/node_heap.py +191 -0
- exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
- exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
- exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
- exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
- exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
- exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
- exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
- exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
- exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
- exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
- exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
- exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
- exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
- exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
- exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
- exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
- exonware/xwnode/strategies/nodes/node_treap.py +387 -0
- exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
- exonware/xwnode/strategies/nodes/node_trie.py +252 -0
- exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
- exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
- exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
- exonware/xwnode/strategies/nodes/queue.py +161 -0
- exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
- exonware/xwnode/strategies/nodes/stack.py +152 -0
- exonware/xwnode/strategies/nodes/trie.py +274 -0
- exonware/xwnode/strategies/nodes/union_find.py +283 -0
- exonware/xwnode/strategies/pattern_detector.py +603 -0
- exonware/xwnode/strategies/performance_monitor.py +487 -0
- exonware/xwnode/strategies/queries/__init__.py +24 -0
- exonware/xwnode/strategies/queries/base.py +236 -0
- exonware/xwnode/strategies/queries/cql.py +201 -0
- exonware/xwnode/strategies/queries/cypher.py +181 -0
- exonware/xwnode/strategies/queries/datalog.py +70 -0
- exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
- exonware/xwnode/strategies/queries/eql.py +70 -0
- exonware/xwnode/strategies/queries/flux.py +70 -0
- exonware/xwnode/strategies/queries/gql.py +70 -0
- exonware/xwnode/strategies/queries/graphql.py +240 -0
- exonware/xwnode/strategies/queries/gremlin.py +181 -0
- exonware/xwnode/strategies/queries/hiveql.py +214 -0
- exonware/xwnode/strategies/queries/hql.py +70 -0
- exonware/xwnode/strategies/queries/jmespath.py +219 -0
- exonware/xwnode/strategies/queries/jq.py +66 -0
- exonware/xwnode/strategies/queries/json_query.py +66 -0
- exonware/xwnode/strategies/queries/jsoniq.py +248 -0
- exonware/xwnode/strategies/queries/kql.py +70 -0
- exonware/xwnode/strategies/queries/linq.py +238 -0
- exonware/xwnode/strategies/queries/logql.py +70 -0
- exonware/xwnode/strategies/queries/mql.py +68 -0
- exonware/xwnode/strategies/queries/n1ql.py +210 -0
- exonware/xwnode/strategies/queries/partiql.py +70 -0
- exonware/xwnode/strategies/queries/pig.py +215 -0
- exonware/xwnode/strategies/queries/promql.py +70 -0
- exonware/xwnode/strategies/queries/sparql.py +220 -0
- exonware/xwnode/strategies/queries/sql.py +275 -0
- exonware/xwnode/strategies/queries/xml_query.py +66 -0
- exonware/xwnode/strategies/queries/xpath.py +223 -0
- exonware/xwnode/strategies/queries/xquery.py +258 -0
- exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
- exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
- exonware/xwnode/strategies/registry.py +604 -0
- exonware/xwnode/strategies/simple.py +273 -0
- exonware/xwnode/strategies/utils.py +532 -0
- exonware/xwnode/types.py +912 -0
- exonware/xwnode/version.py +78 -0
- exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
- exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
- exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
- exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,452 @@
|
|
1
|
+
"""
|
2
|
+
Radix Trie Node Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the RADIX_TRIE strategy for compressed prefix
|
5
|
+
matching with path compression for memory efficiency.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
9
|
+
from .base import ANodeTreeStrategy
|
10
|
+
from ...types import NodeMode, NodeTrait
|
11
|
+
|
12
|
+
|
13
|
+
class RadixTrieNode:
|
14
|
+
"""Node in the radix trie (compressed trie)."""
|
15
|
+
|
16
|
+
def __init__(self, edge_label: str = ""):
|
17
|
+
self.edge_label = edge_label # Compressed edge label
|
18
|
+
self.children: Dict[str, 'RadixTrieNode'] = {}
|
19
|
+
self.is_terminal = False
|
20
|
+
self.value = None
|
21
|
+
self.key = None # Full key that ends at this node
|
22
|
+
|
23
|
+
def is_leaf(self) -> bool:
|
24
|
+
"""Check if this is a leaf node."""
|
25
|
+
return len(self.children) == 0
|
26
|
+
|
27
|
+
|
28
|
+
class RadixTrieStrategy(ANodeTreeStrategy):
|
29
|
+
"""
|
30
|
+
Radix Trie node strategy for compressed prefix matching.
|
31
|
+
|
32
|
+
Provides memory-efficient string storage with path compression
|
33
|
+
and fast prefix-based operations.
|
34
|
+
"""
|
35
|
+
|
36
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
37
|
+
"""Initialize the Radix Trie strategy."""
|
38
|
+
super().__init__(NodeMode.RADIX_TRIE, traits, **options)
|
39
|
+
|
40
|
+
self.case_sensitive = options.get('case_sensitive', True)
|
41
|
+
self.compress_single_child = options.get('compress_single_child', True)
|
42
|
+
|
43
|
+
# Core radix trie
|
44
|
+
self._root = RadixTrieNode()
|
45
|
+
self._size = 0
|
46
|
+
|
47
|
+
# Statistics
|
48
|
+
self._total_nodes = 1 # Root node
|
49
|
+
self._max_depth = 0
|
50
|
+
self._total_compression = 0
|
51
|
+
|
52
|
+
def get_supported_traits(self) -> NodeTrait:
|
53
|
+
"""Get the traits supported by the radix trie strategy."""
|
54
|
+
return (NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.COMPRESSED | NodeTrait.HIERARCHICAL)
|
55
|
+
|
56
|
+
def _normalize_key(self, key: str) -> str:
|
57
|
+
"""Normalize key based on case sensitivity."""
|
58
|
+
return key if self.case_sensitive else key.lower()
|
59
|
+
|
60
|
+
def _find_common_prefix(self, str1: str, str2: str) -> int:
|
61
|
+
"""Find length of common prefix between two strings."""
|
62
|
+
i = 0
|
63
|
+
min_len = min(len(str1), len(str2))
|
64
|
+
while i < min_len and str1[i] == str2[i]:
|
65
|
+
i += 1
|
66
|
+
return i
|
67
|
+
|
68
|
+
def _split_node(self, node: RadixTrieNode, split_pos: int) -> RadixTrieNode:
|
69
|
+
"""Split node at given position in edge label."""
|
70
|
+
if split_pos == 0 or split_pos >= len(node.edge_label):
|
71
|
+
return node
|
72
|
+
|
73
|
+
# Create new intermediate node
|
74
|
+
old_label = node.edge_label
|
75
|
+
new_node = RadixTrieNode(old_label[:split_pos])
|
76
|
+
|
77
|
+
# Update current node
|
78
|
+
node.edge_label = old_label[split_pos:]
|
79
|
+
|
80
|
+
# Move node to be child of new node
|
81
|
+
first_char = node.edge_label[0] if node.edge_label else ""
|
82
|
+
new_node.children[first_char] = node
|
83
|
+
|
84
|
+
self._total_nodes += 1
|
85
|
+
return new_node
|
86
|
+
|
87
|
+
def _insert_recursive(self, node: RadixTrieNode, key: str, value: Any, depth: int = 0) -> RadixTrieNode:
|
88
|
+
"""Recursively insert key-value pair."""
|
89
|
+
self._max_depth = max(self._max_depth, depth)
|
90
|
+
|
91
|
+
if not key:
|
92
|
+
# Reached end of key
|
93
|
+
if not node.is_terminal:
|
94
|
+
self._size += 1
|
95
|
+
node.is_terminal = True
|
96
|
+
node.value = value
|
97
|
+
node.key = key
|
98
|
+
return node
|
99
|
+
|
100
|
+
# Find matching child
|
101
|
+
first_char = key[0]
|
102
|
+
|
103
|
+
if first_char not in node.children:
|
104
|
+
# No matching child, create new one
|
105
|
+
new_node = RadixTrieNode(key)
|
106
|
+
new_node.is_terminal = True
|
107
|
+
new_node.value = value
|
108
|
+
new_node.key = key
|
109
|
+
node.children[first_char] = new_node
|
110
|
+
self._total_nodes += 1
|
111
|
+
self._size += 1
|
112
|
+
return node
|
113
|
+
|
114
|
+
child = node.children[first_char]
|
115
|
+
edge_label = child.edge_label
|
116
|
+
|
117
|
+
# Find common prefix
|
118
|
+
common_prefix_len = self._find_common_prefix(key, edge_label)
|
119
|
+
|
120
|
+
if common_prefix_len == len(edge_label):
|
121
|
+
# Key matches or extends beyond edge label
|
122
|
+
remaining_key = key[common_prefix_len:]
|
123
|
+
self._insert_recursive(child, remaining_key, value, depth + 1)
|
124
|
+
elif common_prefix_len == len(key):
|
125
|
+
# Key is prefix of edge label - need to split
|
126
|
+
split_node = self._split_node(child, common_prefix_len)
|
127
|
+
if not split_node.is_terminal:
|
128
|
+
self._size += 1
|
129
|
+
split_node.is_terminal = True
|
130
|
+
split_node.value = value
|
131
|
+
split_node.key = key
|
132
|
+
node.children[first_char] = split_node
|
133
|
+
else:
|
134
|
+
# Partial match - need to split and create branch
|
135
|
+
split_node = self._split_node(child, common_prefix_len)
|
136
|
+
|
137
|
+
# Insert remaining key
|
138
|
+
remaining_key = key[common_prefix_len:]
|
139
|
+
self._insert_recursive(split_node, remaining_key, value, depth + 1)
|
140
|
+
|
141
|
+
node.children[first_char] = split_node
|
142
|
+
|
143
|
+
return node
|
144
|
+
|
145
|
+
def _find_node(self, key: str) -> Optional[RadixTrieNode]:
|
146
|
+
"""Find node for given key."""
|
147
|
+
normalized_key = self._normalize_key(key)
|
148
|
+
current = self._root
|
149
|
+
remaining = normalized_key
|
150
|
+
|
151
|
+
while remaining and current:
|
152
|
+
first_char = remaining[0]
|
153
|
+
|
154
|
+
if first_char not in current.children:
|
155
|
+
return None
|
156
|
+
|
157
|
+
child = current.children[first_char]
|
158
|
+
edge_label = child.edge_label
|
159
|
+
|
160
|
+
if remaining.startswith(edge_label):
|
161
|
+
# Match found, continue with remaining
|
162
|
+
remaining = remaining[len(edge_label):]
|
163
|
+
current = child
|
164
|
+
else:
|
165
|
+
# Partial match or no match
|
166
|
+
return None
|
167
|
+
|
168
|
+
return current if current and current.is_terminal else None
|
169
|
+
|
170
|
+
def _collect_all_keys(self, node: RadixTrieNode, prefix: str = "") -> List[Tuple[str, Any]]:
|
171
|
+
"""Collect all keys with values starting from given node."""
|
172
|
+
result = []
|
173
|
+
|
174
|
+
current_prefix = prefix + node.edge_label
|
175
|
+
|
176
|
+
if node.is_terminal:
|
177
|
+
result.append((node.key or current_prefix, node.value))
|
178
|
+
|
179
|
+
for child in node.children.values():
|
180
|
+
result.extend(self._collect_all_keys(child, current_prefix))
|
181
|
+
|
182
|
+
return result
|
183
|
+
|
184
|
+
def _collect_prefix_keys(self, node: RadixTrieNode, prefix: str, target_prefix: str) -> List[Tuple[str, Any]]:
|
185
|
+
"""Collect keys with given prefix starting from node."""
|
186
|
+
result = []
|
187
|
+
current_prefix = prefix + node.edge_label
|
188
|
+
|
189
|
+
if current_prefix.startswith(target_prefix):
|
190
|
+
# This path matches prefix
|
191
|
+
if node.is_terminal:
|
192
|
+
result.append((node.key or current_prefix, node.value))
|
193
|
+
|
194
|
+
for child in node.children.values():
|
195
|
+
result.extend(self._collect_prefix_keys(child, current_prefix, target_prefix))
|
196
|
+
elif target_prefix.startswith(current_prefix):
|
197
|
+
# Target prefix extends beyond current path
|
198
|
+
for child in node.children.values():
|
199
|
+
result.extend(self._collect_prefix_keys(child, current_prefix, target_prefix))
|
200
|
+
|
201
|
+
return result
|
202
|
+
|
203
|
+
# ============================================================================
|
204
|
+
# CORE OPERATIONS
|
205
|
+
# ============================================================================
|
206
|
+
|
207
|
+
def put(self, key: Any, value: Any = None) -> None:
|
208
|
+
"""Add key-value pair to radix trie."""
|
209
|
+
key_str = str(key)
|
210
|
+
normalized_key = self._normalize_key(key_str)
|
211
|
+
self._insert_recursive(self._root, normalized_key, value)
|
212
|
+
|
213
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
214
|
+
"""Get value by key."""
|
215
|
+
key_str = str(key)
|
216
|
+
|
217
|
+
if key_str == "trie_info":
|
218
|
+
return {
|
219
|
+
'size': self._size,
|
220
|
+
'total_nodes': self._total_nodes,
|
221
|
+
'max_depth': self._max_depth,
|
222
|
+
'case_sensitive': self.case_sensitive,
|
223
|
+
'compression_ratio': self._total_compression / max(1, self._total_nodes)
|
224
|
+
}
|
225
|
+
elif key_str == "all_keys":
|
226
|
+
all_items = self._collect_all_keys(self._root)
|
227
|
+
return [key for key, _ in all_items]
|
228
|
+
|
229
|
+
node = self._find_node(key_str)
|
230
|
+
return node.value if node else default
|
231
|
+
|
232
|
+
def has(self, key: Any) -> bool:
|
233
|
+
"""Check if key exists."""
|
234
|
+
key_str = str(key)
|
235
|
+
|
236
|
+
if key_str in ["trie_info", "all_keys"]:
|
237
|
+
return True
|
238
|
+
|
239
|
+
return self._find_node(key_str) is not None
|
240
|
+
|
241
|
+
def remove(self, key: Any) -> bool:
|
242
|
+
"""Remove key from trie (simplified implementation)."""
|
243
|
+
key_str = str(key)
|
244
|
+
node = self._find_node(key_str)
|
245
|
+
|
246
|
+
if node and node.is_terminal:
|
247
|
+
node.is_terminal = False
|
248
|
+
node.value = None
|
249
|
+
node.key = None
|
250
|
+
self._size -= 1
|
251
|
+
return True
|
252
|
+
|
253
|
+
return False
|
254
|
+
|
255
|
+
def delete(self, key: Any) -> bool:
|
256
|
+
"""Remove key from trie (alias for remove)."""
|
257
|
+
return self.remove(key)
|
258
|
+
|
259
|
+
def clear(self) -> None:
|
260
|
+
"""Clear all data."""
|
261
|
+
self._root = RadixTrieNode()
|
262
|
+
self._size = 0
|
263
|
+
self._total_nodes = 1
|
264
|
+
self._max_depth = 0
|
265
|
+
self._total_compression = 0
|
266
|
+
|
267
|
+
def keys(self) -> Iterator[str]:
|
268
|
+
"""Get all keys in lexicographic order."""
|
269
|
+
all_items = self._collect_all_keys(self._root)
|
270
|
+
for key, _ in sorted(all_items):
|
271
|
+
yield key
|
272
|
+
|
273
|
+
def values(self) -> Iterator[Any]:
|
274
|
+
"""Get all values in key order."""
|
275
|
+
all_items = self._collect_all_keys(self._root)
|
276
|
+
for _, value in sorted(all_items):
|
277
|
+
yield value
|
278
|
+
|
279
|
+
def items(self) -> Iterator[tuple[str, Any]]:
|
280
|
+
"""Get all key-value pairs in sorted order."""
|
281
|
+
all_items = self._collect_all_keys(self._root)
|
282
|
+
for key, value in sorted(all_items):
|
283
|
+
yield (key, value)
|
284
|
+
|
285
|
+
def __len__(self) -> int:
|
286
|
+
"""Get number of keys."""
|
287
|
+
return self._size
|
288
|
+
|
289
|
+
def to_native(self) -> Dict[str, Any]:
|
290
|
+
"""Convert to native Python dict."""
|
291
|
+
all_items = self._collect_all_keys(self._root)
|
292
|
+
return dict(all_items)
|
293
|
+
|
294
|
+
@property
|
295
|
+
def is_list(self) -> bool:
|
296
|
+
"""This is not a list strategy."""
|
297
|
+
return False
|
298
|
+
|
299
|
+
@property
|
300
|
+
def is_dict(self) -> bool:
|
301
|
+
"""This behaves like a dict."""
|
302
|
+
return True
|
303
|
+
|
304
|
+
# ============================================================================
|
305
|
+
# RADIX TRIE SPECIFIC OPERATIONS
|
306
|
+
# ============================================================================
|
307
|
+
|
308
|
+
def find_with_prefix(self, prefix: str) -> List[Tuple[str, Any]]:
|
309
|
+
"""Find all keys starting with given prefix."""
|
310
|
+
normalized_prefix = self._normalize_key(prefix)
|
311
|
+
return self._collect_prefix_keys(self._root, "", normalized_prefix)
|
312
|
+
|
313
|
+
def get_keys_with_prefix(self, prefix: str) -> List[str]:
|
314
|
+
"""Get keys starting with given prefix."""
|
315
|
+
prefix_items = self.find_with_prefix(prefix)
|
316
|
+
return [key for key, _ in prefix_items]
|
317
|
+
|
318
|
+
def get_values_with_prefix(self, prefix: str) -> List[Any]:
|
319
|
+
"""Get values for keys starting with given prefix."""
|
320
|
+
prefix_items = self.find_with_prefix(prefix)
|
321
|
+
return [value for _, value in prefix_items]
|
322
|
+
|
323
|
+
def count_with_prefix(self, prefix: str) -> int:
|
324
|
+
"""Count keys starting with given prefix."""
|
325
|
+
return len(self.find_with_prefix(prefix))
|
326
|
+
|
327
|
+
def longest_common_prefix(self) -> str:
|
328
|
+
"""Find longest common prefix of all keys."""
|
329
|
+
if self._size == 0:
|
330
|
+
return ""
|
331
|
+
|
332
|
+
all_keys = list(self.keys())
|
333
|
+
if len(all_keys) == 1:
|
334
|
+
return all_keys[0]
|
335
|
+
|
336
|
+
# Find LCP of first and last keys (they're sorted)
|
337
|
+
first_key = all_keys[0]
|
338
|
+
last_key = all_keys[-1]
|
339
|
+
|
340
|
+
common_len = self._find_common_prefix(first_key, last_key)
|
341
|
+
return first_key[:common_len]
|
342
|
+
|
343
|
+
def get_all_prefixes(self, key: str) -> List[str]:
|
344
|
+
"""Get all prefixes of key that exist in trie."""
|
345
|
+
normalized_key = self._normalize_key(key)
|
346
|
+
prefixes = []
|
347
|
+
|
348
|
+
for i in range(1, len(normalized_key) + 1):
|
349
|
+
prefix = normalized_key[:i]
|
350
|
+
if self.has(prefix):
|
351
|
+
prefixes.append(prefix)
|
352
|
+
|
353
|
+
return prefixes
|
354
|
+
|
355
|
+
def is_prefix_of_any(self, prefix: str) -> bool:
|
356
|
+
"""Check if prefix is a prefix of any key in trie."""
|
357
|
+
return len(self.find_with_prefix(prefix)) > 0
|
358
|
+
|
359
|
+
def get_autocomplete_suggestions(self, prefix: str, max_suggestions: int = 10) -> List[str]:
|
360
|
+
"""Get autocomplete suggestions for given prefix."""
|
361
|
+
suggestions = self.get_keys_with_prefix(prefix)
|
362
|
+
return suggestions[:max_suggestions]
|
363
|
+
|
364
|
+
def compute_compression_statistics(self) -> Dict[str, Any]:
|
365
|
+
"""Compute detailed compression statistics."""
|
366
|
+
def _analyze_node(node: RadixTrieNode, depth: int = 0) -> Dict[str, int]:
|
367
|
+
stats = {
|
368
|
+
'nodes': 1,
|
369
|
+
'terminals': 1 if node.is_terminal else 0,
|
370
|
+
'total_edge_length': len(node.edge_label),
|
371
|
+
'compressed_edges': 1 if len(node.edge_label) > 1 else 0,
|
372
|
+
'max_depth': depth
|
373
|
+
}
|
374
|
+
|
375
|
+
for child in node.children.values():
|
376
|
+
child_stats = _analyze_node(child, depth + 1)
|
377
|
+
stats['nodes'] += child_stats['nodes']
|
378
|
+
stats['terminals'] += child_stats['terminals']
|
379
|
+
stats['total_edge_length'] += child_stats['total_edge_length']
|
380
|
+
stats['compressed_edges'] += child_stats['compressed_edges']
|
381
|
+
stats['max_depth'] = max(stats['max_depth'], child_stats['max_depth'])
|
382
|
+
|
383
|
+
return stats
|
384
|
+
|
385
|
+
stats = _analyze_node(self._root)
|
386
|
+
|
387
|
+
# Calculate compression ratio
|
388
|
+
total_key_length = sum(len(key) for key in self.keys())
|
389
|
+
compression_ratio = (stats['total_edge_length'] / max(1, total_key_length)) if total_key_length > 0 else 0
|
390
|
+
|
391
|
+
return {
|
392
|
+
'total_nodes': stats['nodes'],
|
393
|
+
'terminal_nodes': stats['terminals'],
|
394
|
+
'total_edge_length': stats['total_edge_length'],
|
395
|
+
'compressed_edges': stats['compressed_edges'],
|
396
|
+
'max_depth': stats['max_depth'],
|
397
|
+
'compression_ratio': compression_ratio,
|
398
|
+
'avg_edge_length': stats['total_edge_length'] / max(1, stats['nodes']),
|
399
|
+
'keys': self._size
|
400
|
+
}
|
401
|
+
|
402
|
+
def get_statistics(self) -> Dict[str, Any]:
|
403
|
+
"""Get comprehensive radix trie statistics."""
|
404
|
+
compression_stats = self.compute_compression_statistics()
|
405
|
+
|
406
|
+
return {
|
407
|
+
'size': self._size,
|
408
|
+
'total_nodes': self._total_nodes,
|
409
|
+
'max_depth': self._max_depth,
|
410
|
+
'case_sensitive': self.case_sensitive,
|
411
|
+
'compression_statistics': compression_stats,
|
412
|
+
'memory_efficiency': f"{compression_stats['compression_ratio']:.2%}",
|
413
|
+
'avg_key_length': sum(len(key) for key in self.keys()) / max(1, self._size)
|
414
|
+
}
|
415
|
+
|
416
|
+
# ============================================================================
|
417
|
+
# PERFORMANCE CHARACTERISTICS
|
418
|
+
# ============================================================================
|
419
|
+
|
420
|
+
@property
|
421
|
+
def backend_info(self) -> Dict[str, Any]:
|
422
|
+
"""Get backend implementation info."""
|
423
|
+
return {
|
424
|
+
'strategy': 'RADIX_TRIE',
|
425
|
+
'backend': 'Compressed trie with path compression',
|
426
|
+
'case_sensitive': self.case_sensitive,
|
427
|
+
'compress_single_child': self.compress_single_child,
|
428
|
+
'complexity': {
|
429
|
+
'insert': 'O(k)', # k = key length
|
430
|
+
'search': 'O(k)',
|
431
|
+
'delete': 'O(k)',
|
432
|
+
'prefix_search': 'O(k + m)', # m = number of matches
|
433
|
+
'space': 'O(ALPHABET_SIZE * N * M)', # N = nodes, M = avg edge length
|
434
|
+
'compression': 'Path compression reduces space'
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
@property
|
439
|
+
def metrics(self) -> Dict[str, Any]:
|
440
|
+
"""Get performance metrics."""
|
441
|
+
stats = self.get_statistics()
|
442
|
+
comp_stats = stats['compression_statistics']
|
443
|
+
|
444
|
+
return {
|
445
|
+
'size': stats['size'],
|
446
|
+
'total_nodes': stats['total_nodes'],
|
447
|
+
'max_depth': stats['max_depth'],
|
448
|
+
'compression_ratio': f"{comp_stats['compression_ratio']:.2%}",
|
449
|
+
'avg_edge_length': f"{comp_stats['avg_edge_length']:.1f}",
|
450
|
+
'memory_efficiency': stats['memory_efficiency'],
|
451
|
+
'memory_usage': f"{stats['total_nodes'] * 80} bytes (estimated)"
|
452
|
+
}
|