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,565 @@
1
+ """
2
+ Balanced Ordered Map Node Strategy Implementation
3
+
4
+ This module implements the ORDERED_MAP_BALANCED strategy for self-balancing
5
+ ordered operations with guaranteed O(log n) performance.
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 AVLNode:
14
+ """Node in the AVL tree."""
15
+
16
+ def __init__(self, key: str, value: Any):
17
+ self.key = key
18
+ self.value = value
19
+ self.left: Optional['AVLNode'] = None
20
+ self.right: Optional['AVLNode'] = None
21
+ self.height = 1
22
+ self.size = 1 # Size of subtree
23
+
24
+ def update_stats(self) -> None:
25
+ """Update height and size based on children."""
26
+ left_height = self.left.height if self.left else 0
27
+ right_height = self.right.height if self.right else 0
28
+ self.height = max(left_height, right_height) + 1
29
+
30
+ left_size = self.left.size if self.left else 0
31
+ right_size = self.right.size if self.right else 0
32
+ self.size = left_size + right_size + 1
33
+
34
+ def balance_factor(self) -> int:
35
+ """Calculate balance factor."""
36
+ left_height = self.left.height if self.left else 0
37
+ right_height = self.right.height if self.right else 0
38
+ return left_height - right_height
39
+
40
+
41
+ class OrderedMapBalancedStrategy(ANodeTreeStrategy):
42
+ """
43
+ Balanced Ordered Map node strategy using AVL tree.
44
+
45
+ Provides guaranteed O(log n) operations with automatic balancing
46
+ for optimal performance in all scenarios.
47
+ """
48
+
49
+ def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
50
+ """Initialize the Balanced Ordered Map strategy."""
51
+ super().__init__(NodeMode.ORDERED_MAP_BALANCED, traits, **options)
52
+
53
+ self.case_sensitive = options.get('case_sensitive', True)
54
+ self.allow_duplicates = options.get('allow_duplicates', False)
55
+
56
+ # Core AVL tree
57
+ self._root: Optional[AVLNode] = None
58
+ self._size = 0
59
+
60
+ # Statistics
61
+ self._rotations = 0
62
+ self._max_height = 0
63
+ self._rebalances = 0
64
+
65
+ def get_supported_traits(self) -> NodeTrait:
66
+ """Get the traits supported by the balanced ordered map strategy."""
67
+ return (NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.HIERARCHICAL)
68
+
69
+ def _normalize_key(self, key: str) -> str:
70
+ """Normalize key based on case sensitivity."""
71
+ return key if self.case_sensitive else key.lower()
72
+
73
+ def _rotate_right(self, y: AVLNode) -> AVLNode:
74
+ """Perform right rotation."""
75
+ x = y.left
76
+ T2 = x.right
77
+
78
+ # Perform rotation
79
+ x.right = y
80
+ y.left = T2
81
+
82
+ # Update heights and sizes
83
+ y.update_stats()
84
+ x.update_stats()
85
+
86
+ self._rotations += 1
87
+ return x
88
+
89
+ def _rotate_left(self, x: AVLNode) -> AVLNode:
90
+ """Perform left rotation."""
91
+ y = x.right
92
+ T2 = y.left
93
+
94
+ # Perform rotation
95
+ y.left = x
96
+ x.right = T2
97
+
98
+ # Update heights and sizes
99
+ x.update_stats()
100
+ y.update_stats()
101
+
102
+ self._rotations += 1
103
+ return y
104
+
105
+ def _insert_node(self, node: Optional[AVLNode], key: str, value: Any) -> AVLNode:
106
+ """Insert key-value pair into AVL tree."""
107
+ normalized_key = self._normalize_key(key)
108
+
109
+ # 1. Perform normal BST insertion
110
+ if not node:
111
+ self._size += 1
112
+ return AVLNode(key, value)
113
+
114
+ if normalized_key < self._normalize_key(node.key):
115
+ node.left = self._insert_node(node.left, key, value)
116
+ elif normalized_key > self._normalize_key(node.key):
117
+ node.right = self._insert_node(node.right, key, value)
118
+ else:
119
+ # Key already exists
120
+ if not self.allow_duplicates:
121
+ node.value = value # Update existing value
122
+ return node
123
+ else:
124
+ # For duplicates, insert to right
125
+ node.right = self._insert_node(node.right, key, value)
126
+
127
+ # 2. Update height and size
128
+ node.update_stats()
129
+ self._max_height = max(self._max_height, node.height)
130
+
131
+ # 3. Get balance factor
132
+ balance = node.balance_factor()
133
+
134
+ # 4. If unbalanced, perform rotations
135
+ if balance > 1:
136
+ # Left heavy
137
+ if normalized_key < self._normalize_key(node.left.key):
138
+ # Left-Left case
139
+ self._rebalances += 1
140
+ return self._rotate_right(node)
141
+ else:
142
+ # Left-Right case
143
+ self._rebalances += 1
144
+ node.left = self._rotate_left(node.left)
145
+ return self._rotate_right(node)
146
+
147
+ if balance < -1:
148
+ # Right heavy
149
+ if normalized_key > self._normalize_key(node.right.key):
150
+ # Right-Right case
151
+ self._rebalances += 1
152
+ return self._rotate_left(node)
153
+ else:
154
+ # Right-Left case
155
+ self._rebalances += 1
156
+ node.right = self._rotate_right(node.right)
157
+ return self._rotate_left(node)
158
+
159
+ return node
160
+
161
+ def _find_node(self, node: Optional[AVLNode], key: str) -> Optional[AVLNode]:
162
+ """Find node with given key."""
163
+ if not node:
164
+ return None
165
+
166
+ normalized_key = self._normalize_key(key)
167
+ node_key_norm = self._normalize_key(node.key)
168
+
169
+ if normalized_key == node_key_norm:
170
+ return node
171
+ elif normalized_key < node_key_norm:
172
+ return self._find_node(node.left, key)
173
+ else:
174
+ return self._find_node(node.right, key)
175
+
176
+ def _find_min(self, node: AVLNode) -> AVLNode:
177
+ """Find minimum node in subtree."""
178
+ while node.left:
179
+ node = node.left
180
+ return node
181
+
182
+ def _delete_node(self, node: Optional[AVLNode], key: str) -> Optional[AVLNode]:
183
+ """Delete node with given key."""
184
+ if not node:
185
+ return None
186
+
187
+ normalized_key = self._normalize_key(key)
188
+ node_key_norm = self._normalize_key(node.key)
189
+
190
+ if normalized_key < node_key_norm:
191
+ node.left = self._delete_node(node.left, key)
192
+ elif normalized_key > node_key_norm:
193
+ node.right = self._delete_node(node.right, key)
194
+ else:
195
+ # Node to be deleted found
196
+ self._size -= 1
197
+
198
+ if not node.left or not node.right:
199
+ # Node with 0 or 1 child
200
+ temp = node.left if node.left else node.right
201
+ if not temp:
202
+ # No child case
203
+ return None
204
+ else:
205
+ # One child case
206
+ return temp
207
+ else:
208
+ # Node with 2 children
209
+ temp = self._find_min(node.right)
210
+ node.key = temp.key
211
+ node.value = temp.value
212
+ node.right = self._delete_node(node.right, temp.key)
213
+
214
+ # Update height and size
215
+ node.update_stats()
216
+
217
+ # Get balance factor
218
+ balance = node.balance_factor()
219
+
220
+ # Rebalance if needed
221
+ if balance > 1:
222
+ if node.left and node.left.balance_factor() >= 0:
223
+ # Left-Left case
224
+ self._rebalances += 1
225
+ return self._rotate_right(node)
226
+ else:
227
+ # Left-Right case
228
+ self._rebalances += 1
229
+ if node.left:
230
+ node.left = self._rotate_left(node.left)
231
+ return self._rotate_right(node)
232
+
233
+ if balance < -1:
234
+ if node.right and node.right.balance_factor() <= 0:
235
+ # Right-Right case
236
+ self._rebalances += 1
237
+ return self._rotate_left(node)
238
+ else:
239
+ # Right-Left case
240
+ self._rebalances += 1
241
+ if node.right:
242
+ node.right = self._rotate_right(node.right)
243
+ return self._rotate_left(node)
244
+
245
+ return node
246
+
247
+ def _inorder_traversal(self, node: Optional[AVLNode], result: List[Tuple[str, Any]]) -> None:
248
+ """Inorder traversal to collect key-value pairs."""
249
+ if node:
250
+ self._inorder_traversal(node.left, result)
251
+ result.append((node.key, node.value))
252
+ self._inorder_traversal(node.right, result)
253
+
254
+ def _range_query(self, node: Optional[AVLNode], start: str, end: str, inclusive: bool, result: List[Tuple[str, Any]]) -> None:
255
+ """Collect nodes in range."""
256
+ if not node:
257
+ return
258
+
259
+ node_key_norm = self._normalize_key(node.key)
260
+ start_norm = self._normalize_key(start)
261
+ end_norm = self._normalize_key(end)
262
+
263
+ # Check if we should go left
264
+ if node_key_norm > start_norm or (inclusive and node_key_norm >= start_norm):
265
+ self._range_query(node.left, start, end, inclusive, result)
266
+
267
+ # Check if current node is in range
268
+ if inclusive:
269
+ if start_norm <= node_key_norm <= end_norm:
270
+ result.append((node.key, node.value))
271
+ else:
272
+ if start_norm < node_key_norm < end_norm:
273
+ result.append((node.key, node.value))
274
+
275
+ # Check if we should go right
276
+ if node_key_norm < end_norm or (inclusive and node_key_norm <= end_norm):
277
+ self._range_query(node.right, start, end, inclusive, result)
278
+
279
+ # ============================================================================
280
+ # CORE OPERATIONS
281
+ # ============================================================================
282
+
283
+ def put(self, key: Any, value: Any = None) -> None:
284
+ """Add key-value pair to balanced tree."""
285
+ key_str = str(key)
286
+ self._root = self._insert_node(self._root, key_str, value)
287
+
288
+ def get(self, key: Any, default: Any = None) -> Any:
289
+ """Get value by key."""
290
+ key_str = str(key)
291
+
292
+ if key_str == "tree_info":
293
+ return {
294
+ 'size': self._size,
295
+ 'height': self._root.height if self._root else 0,
296
+ 'max_height': self._max_height,
297
+ 'rotations': self._rotations,
298
+ 'rebalances': self._rebalances,
299
+ 'case_sensitive': self.case_sensitive,
300
+ 'balance_factor': self._root.balance_factor() if self._root else 0
301
+ }
302
+ elif key_str == "balance_stats":
303
+ return self.get_balance_statistics()
304
+ elif key_str.isdigit():
305
+ # Access by index
306
+ index = int(key_str)
307
+ return self.get_at_index(index)
308
+
309
+ node = self._find_node(self._root, key_str)
310
+ return node.value if node else default
311
+
312
+ def has(self, key: Any) -> bool:
313
+ """Check if key exists."""
314
+ key_str = str(key)
315
+
316
+ if key_str in ["tree_info", "balance_stats"]:
317
+ return True
318
+ elif key_str.isdigit():
319
+ index = int(key_str)
320
+ return 0 <= index < self._size
321
+
322
+ return self._find_node(self._root, key_str) is not None
323
+
324
+ def remove(self, key: Any) -> bool:
325
+ """Remove key from tree."""
326
+ key_str = str(key)
327
+
328
+ if key_str.isdigit():
329
+ # Remove by index
330
+ index = int(key_str)
331
+ return self.remove_at_index(index)
332
+
333
+ old_size = self._size
334
+ self._root = self._delete_node(self._root, key_str)
335
+ return self._size < old_size
336
+
337
+ def delete(self, key: Any) -> bool:
338
+ """Remove key from tree (alias for remove)."""
339
+ return self.remove(key)
340
+
341
+ def clear(self) -> None:
342
+ """Clear all data."""
343
+ self._root = None
344
+ self._size = 0
345
+ self._rotations = 0
346
+ self._max_height = 0
347
+ self._rebalances = 0
348
+
349
+ def keys(self) -> Iterator[str]:
350
+ """Get all keys in sorted order."""
351
+ result = []
352
+ self._inorder_traversal(self._root, result)
353
+ for key, _ in result:
354
+ yield key
355
+
356
+ def values(self) -> Iterator[Any]:
357
+ """Get all values in key order."""
358
+ result = []
359
+ self._inorder_traversal(self._root, result)
360
+ for _, value in result:
361
+ yield value
362
+
363
+ def items(self) -> Iterator[tuple[str, Any]]:
364
+ """Get all key-value pairs in sorted order."""
365
+ result = []
366
+ self._inorder_traversal(self._root, result)
367
+ for key, value in result:
368
+ yield (key, value)
369
+
370
+ def __len__(self) -> int:
371
+ """Get number of key-value pairs."""
372
+ return self._size
373
+
374
+ def to_native(self) -> Dict[str, Any]:
375
+ """Convert to native Python dict."""
376
+ return dict(self.items())
377
+
378
+ @property
379
+ def is_list(self) -> bool:
380
+ """This can behave like a list for indexed access."""
381
+ return True
382
+
383
+ @property
384
+ def is_dict(self) -> bool:
385
+ """This is a dict-like structure."""
386
+ return True
387
+
388
+ # ============================================================================
389
+ # BALANCED TREE SPECIFIC OPERATIONS
390
+ # ============================================================================
391
+
392
+ def first_key(self) -> Optional[str]:
393
+ """Get first (smallest) key."""
394
+ if not self._root:
395
+ return None
396
+
397
+ node = self._root
398
+ while node.left:
399
+ node = node.left
400
+ return node.key
401
+
402
+ def last_key(self) -> Optional[str]:
403
+ """Get last (largest) key."""
404
+ if not self._root:
405
+ return None
406
+
407
+ node = self._root
408
+ while node.right:
409
+ node = node.right
410
+ return node.key
411
+
412
+ def get_range(self, start_key: str, end_key: str, inclusive: bool = True) -> List[Tuple[str, Any]]:
413
+ """Get key-value pairs in range with O(log n + k) complexity."""
414
+ result = []
415
+ self._range_query(self._root, start_key, end_key, inclusive, result)
416
+ return result
417
+
418
+ def get_at_index(self, index: int) -> Optional[Any]:
419
+ """Get value at specific index with O(log n) complexity."""
420
+ if index < 0 or index >= self._size:
421
+ return None
422
+
423
+ def _find_by_index(node: Optional[AVLNode], target_index: int) -> Optional[Any]:
424
+ if not node:
425
+ return None
426
+
427
+ left_size = node.left.size if node.left else 0
428
+
429
+ if target_index == left_size:
430
+ return node.value
431
+ elif target_index < left_size:
432
+ return _find_by_index(node.left, target_index)
433
+ else:
434
+ return _find_by_index(node.right, target_index - left_size - 1)
435
+
436
+ return _find_by_index(self._root, index)
437
+
438
+ def index_of(self, key: str) -> int:
439
+ """Get index of key with O(log n) complexity."""
440
+ def _find_index(node: Optional[AVLNode], target_key: str, current_index: int = 0) -> int:
441
+ if not node:
442
+ return -1
443
+
444
+ normalized_target = self._normalize_key(target_key)
445
+ normalized_node = self._normalize_key(node.key)
446
+
447
+ left_size = node.left.size if node.left else 0
448
+
449
+ if normalized_target == normalized_node:
450
+ return current_index + left_size
451
+ elif normalized_target < normalized_node:
452
+ return _find_index(node.left, target_key, current_index)
453
+ else:
454
+ return _find_index(node.right, target_key, current_index + left_size + 1)
455
+
456
+ return _find_index(self._root, key)
457
+
458
+ def remove_at_index(self, index: int) -> bool:
459
+ """Remove element at specific index."""
460
+ if index < 0 or index >= self._size:
461
+ return False
462
+
463
+ # Find key at index first
464
+ def _key_at_index(node: Optional[AVLNode], target_index: int) -> Optional[str]:
465
+ if not node:
466
+ return None
467
+
468
+ left_size = node.left.size if node.left else 0
469
+
470
+ if target_index == left_size:
471
+ return node.key
472
+ elif target_index < left_size:
473
+ return _key_at_index(node.left, target_index)
474
+ else:
475
+ return _key_at_index(node.right, target_index - left_size - 1)
476
+
477
+ key = _key_at_index(self._root, index)
478
+ if key:
479
+ return self.remove(key)
480
+ return False
481
+
482
+ def get_balance_statistics(self) -> Dict[str, Any]:
483
+ """Get comprehensive balance statistics."""
484
+ def _analyze_balance(node: Optional[AVLNode]) -> Dict[str, Any]:
485
+ if not node:
486
+ return {
487
+ 'nodes': 0,
488
+ 'height': 0,
489
+ 'balance_factors': [],
490
+ 'perfect_balance': True,
491
+ 'max_imbalance': 0
492
+ }
493
+
494
+ left_stats = _analyze_balance(node.left)
495
+ right_stats = _analyze_balance(node.right)
496
+
497
+ balance_factor = node.balance_factor()
498
+ balance_factors = left_stats['balance_factors'] + right_stats['balance_factors'] + [balance_factor]
499
+
500
+ return {
501
+ 'nodes': 1 + left_stats['nodes'] + right_stats['nodes'],
502
+ 'height': node.height,
503
+ 'balance_factors': balance_factors,
504
+ 'perfect_balance': left_stats['perfect_balance'] and right_stats['perfect_balance'] and abs(balance_factor) <= 1,
505
+ 'max_imbalance': max(abs(balance_factor), left_stats['max_imbalance'], right_stats['max_imbalance'])
506
+ }
507
+
508
+ stats = _analyze_balance(self._root)
509
+
510
+ # Calculate theoretical minimum height
511
+ import math
512
+ theoretical_min_height = math.ceil(math.log2(self._size + 1)) if self._size > 0 else 0
513
+
514
+ return {
515
+ 'size': self._size,
516
+ 'height': stats['height'],
517
+ 'theoretical_min_height': theoretical_min_height,
518
+ 'height_efficiency': theoretical_min_height / max(1, stats['height']),
519
+ 'total_rotations': self._rotations,
520
+ 'total_rebalances': self._rebalances,
521
+ 'perfect_balance': stats['perfect_balance'],
522
+ 'max_imbalance': stats['max_imbalance'],
523
+ 'avg_balance_factor': sum(stats['balance_factors']) / max(1, len(stats['balance_factors'])),
524
+ 'is_balanced': stats['max_imbalance'] <= 1
525
+ }
526
+
527
+ # ============================================================================
528
+ # PERFORMANCE CHARACTERISTICS
529
+ # ============================================================================
530
+
531
+ @property
532
+ def backend_info(self) -> Dict[str, Any]:
533
+ """Get backend implementation info."""
534
+ return {
535
+ 'strategy': 'ORDERED_MAP_BALANCED',
536
+ 'backend': 'Self-balancing AVL tree',
537
+ 'case_sensitive': self.case_sensitive,
538
+ 'allow_duplicates': self.allow_duplicates,
539
+ 'complexity': {
540
+ 'insert': 'O(log n)',
541
+ 'search': 'O(log n)',
542
+ 'delete': 'O(log n)',
543
+ 'range_query': 'O(log n + k)', # k = result size
544
+ 'index_access': 'O(log n)',
545
+ 'balance_operations': 'O(1) per rotation',
546
+ 'space': 'O(n)',
547
+ 'height_guarantee': 'O(log n)'
548
+ }
549
+ }
550
+
551
+ @property
552
+ def metrics(self) -> Dict[str, Any]:
553
+ """Get performance metrics."""
554
+ balance_stats = self.get_balance_statistics()
555
+
556
+ return {
557
+ 'size': balance_stats['size'],
558
+ 'height': balance_stats['height'],
559
+ 'height_efficiency': f"{balance_stats['height_efficiency'] * 100:.1f}%",
560
+ 'total_rotations': balance_stats['total_rotations'],
561
+ 'total_rebalances': balance_stats['total_rebalances'],
562
+ 'is_balanced': balance_stats['is_balanced'],
563
+ 'max_imbalance': balance_stats['max_imbalance'],
564
+ 'memory_usage': f"{balance_stats['size'] * 80} bytes (estimated)"
565
+ }