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,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
|
+
}
|