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.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. 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
+ }