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,542 @@
1
+ """
2
+ B+ Tree Node Strategy Implementation
3
+
4
+ This module implements the B_PLUS_TREE strategy for database-friendly
5
+ operations with efficient range queries and sequential access.
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 BPlusTreeNode:
14
+ """Node in the B+ tree."""
15
+
16
+ def __init__(self, is_leaf: bool = False, max_keys: int = 4):
17
+ self.is_leaf = is_leaf
18
+ self.keys: List[str] = []
19
+ self.values: List[Any] = [] if is_leaf else [] # Only leaves store values
20
+ self.children: List['BPlusTreeNode'] = [] if not is_leaf else [] # Internal nodes have children
21
+ self.next_leaf: Optional['BPlusTreeNode'] = None # Leaf linking for sequential access
22
+ self.parent: Optional['BPlusTreeNode'] = None
23
+ self.max_keys = max_keys
24
+
25
+ def is_full(self) -> bool:
26
+ """Check if node is full."""
27
+ return len(self.keys) >= self.max_keys
28
+
29
+ def is_underflow(self) -> bool:
30
+ """Check if node has too few keys."""
31
+ min_keys = self.max_keys // 2
32
+ return len(self.keys) < min_keys
33
+
34
+ def find_child_index(self, key: str) -> int:
35
+ """Find child index for given key."""
36
+ for i, k in enumerate(self.keys):
37
+ if key <= k:
38
+ return i
39
+ return len(self.keys)
40
+
41
+
42
+ class BPlusTreeStrategy(ANodeTreeStrategy):
43
+ """
44
+ B+ Tree node strategy for database-friendly operations.
45
+
46
+ Provides efficient range queries, sequential access, and balanced
47
+ tree operations optimized for disk-based storage systems.
48
+ """
49
+
50
+ def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
51
+ """Initialize the B+ Tree strategy."""
52
+ super().__init__(NodeMode.B_PLUS_TREE, traits, **options)
53
+
54
+ self.order = options.get('order', 4) # Maximum number of keys per node
55
+ self.case_sensitive = options.get('case_sensitive', True)
56
+
57
+ # Core B+ tree
58
+ self._root: Optional[BPlusTreeNode] = None
59
+ self._first_leaf: Optional[BPlusTreeNode] = None # Pointer to first leaf for iteration
60
+ self._size = 0
61
+
62
+ # Statistics
63
+ self._height = 0
64
+ self._total_nodes = 0
65
+ self._total_splits = 0
66
+ self._total_merges = 0
67
+
68
+ def get_supported_traits(self) -> NodeTrait:
69
+ """Get the traits supported by the B+ tree strategy."""
70
+ return (NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.HIERARCHICAL | NodeTrait.PERSISTENT)
71
+
72
+ def _normalize_key(self, key: str) -> str:
73
+ """Normalize key based on case sensitivity."""
74
+ return key if self.case_sensitive else key.lower()
75
+
76
+ def _create_leaf_node(self) -> BPlusTreeNode:
77
+ """Create new leaf node."""
78
+ node = BPlusTreeNode(is_leaf=True, max_keys=self.order)
79
+ self._total_nodes += 1
80
+ return node
81
+
82
+ def _create_internal_node(self) -> BPlusTreeNode:
83
+ """Create new internal node."""
84
+ node = BPlusTreeNode(is_leaf=False, max_keys=self.order)
85
+ self._total_nodes += 1
86
+ return node
87
+
88
+ def _find_leaf(self, key: str) -> Optional[BPlusTreeNode]:
89
+ """Find leaf node that should contain the key."""
90
+ if not self._root:
91
+ return None
92
+
93
+ current = self._root
94
+ while not current.is_leaf:
95
+ child_index = current.find_child_index(key)
96
+ current = current.children[child_index]
97
+
98
+ return current
99
+
100
+ def _insert_into_leaf(self, leaf: BPlusTreeNode, key: str, value: Any) -> None:
101
+ """Insert key-value pair into leaf node."""
102
+ # Find insertion position
103
+ pos = 0
104
+ while pos < len(leaf.keys) and leaf.keys[pos] < key:
105
+ pos += 1
106
+
107
+ # Check if key already exists
108
+ if pos < len(leaf.keys) and leaf.keys[pos] == key:
109
+ leaf.values[pos] = value # Update existing value
110
+ return
111
+
112
+ # Insert new key-value pair
113
+ leaf.keys.insert(pos, key)
114
+ leaf.values.insert(pos, value)
115
+ self._size += 1
116
+
117
+ def _split_leaf(self, leaf: BPlusTreeNode) -> Tuple[BPlusTreeNode, str]:
118
+ """Split full leaf node."""
119
+ mid = len(leaf.keys) // 2
120
+ new_leaf = self._create_leaf_node()
121
+
122
+ # Move half of keys/values to new leaf
123
+ new_leaf.keys = leaf.keys[mid:]
124
+ new_leaf.values = leaf.values[mid:]
125
+
126
+ # Update original leaf
127
+ leaf.keys = leaf.keys[:mid]
128
+ leaf.values = leaf.values[:mid]
129
+
130
+ # Link leaves
131
+ new_leaf.next_leaf = leaf.next_leaf
132
+ leaf.next_leaf = new_leaf
133
+
134
+ # Set parent
135
+ new_leaf.parent = leaf.parent
136
+
137
+ self._total_splits += 1
138
+ return new_leaf, new_leaf.keys[0] # Return new node and separator key
139
+
140
+ def _split_internal(self, node: BPlusTreeNode) -> Tuple[BPlusTreeNode, str]:
141
+ """Split full internal node."""
142
+ mid = len(node.keys) // 2
143
+ new_node = self._create_internal_node()
144
+
145
+ # Move keys and children
146
+ separator_key = node.keys[mid]
147
+ new_node.keys = node.keys[mid + 1:]
148
+ new_node.children = node.children[mid + 1:]
149
+
150
+ # Update original node
151
+ node.keys = node.keys[:mid]
152
+ node.children = node.children[:mid + 1]
153
+
154
+ # Update parent pointers
155
+ new_node.parent = node.parent
156
+ for child in new_node.children:
157
+ child.parent = new_node
158
+
159
+ self._total_splits += 1
160
+ return new_node, separator_key
161
+
162
+ def _insert_into_parent(self, left: BPlusTreeNode, key: str, right: BPlusTreeNode) -> None:
163
+ """Insert separator key into parent after split."""
164
+ if left.parent is None:
165
+ # Create new root
166
+ new_root = self._create_internal_node()
167
+ new_root.keys = [key]
168
+ new_root.children = [left, right]
169
+ left.parent = new_root
170
+ right.parent = new_root
171
+ self._root = new_root
172
+ self._height += 1
173
+ return
174
+
175
+ parent = left.parent
176
+
177
+ # Find insertion position
178
+ pos = 0
179
+ while pos < len(parent.keys) and parent.keys[pos] < key:
180
+ pos += 1
181
+
182
+ # Insert key and child
183
+ parent.keys.insert(pos, key)
184
+ parent.children.insert(pos + 1, right)
185
+ right.parent = parent
186
+
187
+ # Check if parent is full
188
+ if parent.is_full():
189
+ new_parent, separator = self._split_internal(parent)
190
+ self._insert_into_parent(parent, separator, new_parent)
191
+
192
+ def _insert_key(self, key: str, value: Any) -> None:
193
+ """Insert key-value pair into B+ tree."""
194
+ if not self._root:
195
+ # Create first leaf node
196
+ self._root = self._create_leaf_node()
197
+ self._first_leaf = self._root
198
+ self._height = 1
199
+
200
+ leaf = self._find_leaf(key)
201
+ if not leaf:
202
+ return
203
+
204
+ self._insert_into_leaf(leaf, key, value)
205
+
206
+ # Check if leaf is full
207
+ if leaf.is_full():
208
+ new_leaf, separator = self._split_leaf(leaf)
209
+ self._insert_into_parent(leaf, separator, new_leaf)
210
+
211
+ def _search_key(self, key: str) -> Optional[Any]:
212
+ """Search for key in B+ tree."""
213
+ leaf = self._find_leaf(key)
214
+ if not leaf:
215
+ return None
216
+
217
+ # Search in leaf
218
+ for i, k in enumerate(leaf.keys):
219
+ if k == key:
220
+ return leaf.values[i]
221
+
222
+ return None
223
+
224
+ # ============================================================================
225
+ # CORE OPERATIONS
226
+ # ============================================================================
227
+
228
+ def put(self, key: Any, value: Any = None) -> None:
229
+ """Add key-value pair to B+ tree."""
230
+ key_str = str(key)
231
+ normalized_key = self._normalize_key(key_str)
232
+ self._insert_key(normalized_key, value)
233
+
234
+ def get(self, key: Any, default: Any = None) -> Any:
235
+ """Get value by key."""
236
+ key_str = str(key)
237
+
238
+ if key_str == "tree_info":
239
+ return {
240
+ 'size': self._size,
241
+ 'height': self._height,
242
+ 'total_nodes': self._total_nodes,
243
+ 'order': self.order,
244
+ 'case_sensitive': self.case_sensitive,
245
+ 'total_splits': self._total_splits
246
+ }
247
+ elif key_str == "first_key":
248
+ return self.first_key()
249
+ elif key_str == "last_key":
250
+ return self.last_key()
251
+ elif key_str.isdigit():
252
+ # Access by index
253
+ index = int(key_str)
254
+ return self.get_at_index(index)
255
+
256
+ normalized_key = self._normalize_key(key_str)
257
+ result = self._search_key(normalized_key)
258
+ return result if result is not None else default
259
+
260
+ def has(self, key: Any) -> bool:
261
+ """Check if key exists."""
262
+ key_str = str(key)
263
+
264
+ if key_str in ["tree_info", "first_key", "last_key"]:
265
+ return True
266
+ elif key_str.isdigit():
267
+ index = int(key_str)
268
+ return 0 <= index < self._size
269
+
270
+ normalized_key = self._normalize_key(key_str)
271
+ return self._search_key(normalized_key) is not None
272
+
273
+ def remove(self, key: Any) -> bool:
274
+ """Remove key from tree (simplified implementation)."""
275
+ key_str = str(key)
276
+ normalized_key = self._normalize_key(key_str)
277
+
278
+ leaf = self._find_leaf(normalized_key)
279
+ if not leaf:
280
+ return False
281
+
282
+ # Find and remove key
283
+ for i, k in enumerate(leaf.keys):
284
+ if k == normalized_key:
285
+ del leaf.keys[i]
286
+ del leaf.values[i]
287
+ self._size -= 1
288
+ return True
289
+
290
+ return False
291
+
292
+ def delete(self, key: Any) -> bool:
293
+ """Remove key from tree (alias for remove)."""
294
+ return self.remove(key)
295
+
296
+ def clear(self) -> None:
297
+ """Clear all data."""
298
+ self._root = None
299
+ self._first_leaf = None
300
+ self._size = 0
301
+ self._height = 0
302
+ self._total_nodes = 0
303
+ self._total_splits = 0
304
+ self._total_merges = 0
305
+
306
+ def keys(self) -> Iterator[str]:
307
+ """Get all keys in sorted order."""
308
+ current = self._first_leaf
309
+ while current:
310
+ for key in current.keys:
311
+ yield key
312
+ current = current.next_leaf
313
+
314
+ def values(self) -> Iterator[Any]:
315
+ """Get all values in key order."""
316
+ current = self._first_leaf
317
+ while current:
318
+ for value in current.values:
319
+ yield value
320
+ current = current.next_leaf
321
+
322
+ def items(self) -> Iterator[tuple[str, Any]]:
323
+ """Get all key-value pairs in sorted order."""
324
+ current = self._first_leaf
325
+ while current:
326
+ for key, value in zip(current.keys, current.values):
327
+ yield (key, value)
328
+ current = current.next_leaf
329
+
330
+ def __len__(self) -> int:
331
+ """Get number of key-value pairs."""
332
+ return self._size
333
+
334
+ def to_native(self) -> Dict[str, Any]:
335
+ """Convert to native Python dict."""
336
+ return dict(self.items())
337
+
338
+ @property
339
+ def is_list(self) -> bool:
340
+ """This can behave like a list for indexed access."""
341
+ return True
342
+
343
+ @property
344
+ def is_dict(self) -> bool:
345
+ """This is a dict-like structure."""
346
+ return True
347
+
348
+ # ============================================================================
349
+ # B+ TREE SPECIFIC OPERATIONS
350
+ # ============================================================================
351
+
352
+ def first_key(self) -> Optional[str]:
353
+ """Get first (smallest) key."""
354
+ if self._first_leaf and self._first_leaf.keys:
355
+ return self._first_leaf.keys[0]
356
+ return None
357
+
358
+ def last_key(self) -> Optional[str]:
359
+ """Get last (largest) key."""
360
+ current = self._first_leaf
361
+ last_leaf = None
362
+
363
+ while current:
364
+ last_leaf = current
365
+ current = current.next_leaf
366
+
367
+ if last_leaf and last_leaf.keys:
368
+ return last_leaf.keys[-1]
369
+ return None
370
+
371
+ def get_range(self, start_key: str, end_key: str, inclusive: bool = True) -> List[Tuple[str, Any]]:
372
+ """Get key-value pairs in range."""
373
+ start_norm = self._normalize_key(start_key)
374
+ end_norm = self._normalize_key(end_key)
375
+
376
+ result = []
377
+ current = self._first_leaf
378
+
379
+ while current:
380
+ for key, value in zip(current.keys, current.values):
381
+ if inclusive:
382
+ if start_norm <= key <= end_norm:
383
+ result.append((key, value))
384
+ else:
385
+ if start_norm < key < end_norm:
386
+ result.append((key, value))
387
+
388
+ if key > end_norm:
389
+ return result
390
+
391
+ current = current.next_leaf
392
+
393
+ return result
394
+
395
+ def get_at_index(self, index: int) -> Optional[Any]:
396
+ """Get value at specific index."""
397
+ if index < 0 or index >= self._size:
398
+ return None
399
+
400
+ current_index = 0
401
+ current = self._first_leaf
402
+
403
+ while current:
404
+ if current_index + len(current.keys) > index:
405
+ local_index = index - current_index
406
+ return current.values[local_index]
407
+
408
+ current_index += len(current.keys)
409
+ current = current.next_leaf
410
+
411
+ return None
412
+
413
+ def index_of(self, key: str) -> int:
414
+ """Get index of key (-1 if not found)."""
415
+ normalized_key = self._normalize_key(key)
416
+ current_index = 0
417
+ current = self._first_leaf
418
+
419
+ while current:
420
+ for i, k in enumerate(current.keys):
421
+ if k == normalized_key:
422
+ return current_index + i
423
+
424
+ current_index += len(current.keys)
425
+ current = current.next_leaf
426
+
427
+ return -1
428
+
429
+ def find_prefix_keys(self, prefix: str) -> List[str]:
430
+ """Find all keys starting with given prefix."""
431
+ normalized_prefix = self._normalize_key(prefix)
432
+ result = []
433
+
434
+ current = self._first_leaf
435
+ while current:
436
+ for key in current.keys:
437
+ if key.startswith(normalized_prefix):
438
+ result.append(key)
439
+ elif key > normalized_prefix and not key.startswith(normalized_prefix):
440
+ return result # No more matches possible
441
+ current = current.next_leaf
442
+
443
+ return result
444
+
445
+ def bulk_load(self, items: List[Tuple[str, Any]]) -> None:
446
+ """Bulk load sorted key-value pairs (more efficient than individual inserts)."""
447
+ self.clear()
448
+
449
+ # Sort items
450
+ sorted_items = sorted(items, key=lambda x: self._normalize_key(x[0]))
451
+
452
+ for key, value in sorted_items:
453
+ self.put(key, value)
454
+
455
+ def get_tree_statistics(self) -> Dict[str, Any]:
456
+ """Get detailed tree statistics."""
457
+ if not self._root:
458
+ return {'size': 0, 'height': 0, 'nodes': 0}
459
+
460
+ # Analyze tree structure
461
+ def _analyze_level(nodes: List[BPlusTreeNode], level: int) -> Dict[str, Any]:
462
+ if not nodes:
463
+ return {'levels': level, 'leaf_nodes': 0, 'internal_nodes': 0, 'total_keys': 0}
464
+
465
+ level_stats = {
466
+ 'level': level,
467
+ 'nodes_at_level': len(nodes),
468
+ 'keys_at_level': sum(len(node.keys) for node in nodes),
469
+ 'leaf_nodes_at_level': sum(1 for node in nodes if node.is_leaf),
470
+ 'internal_nodes_at_level': sum(1 for node in nodes if not node.is_leaf)
471
+ }
472
+
473
+ # Get next level
474
+ next_level_nodes = []
475
+ for node in nodes:
476
+ if not node.is_leaf:
477
+ next_level_nodes.extend(node.children)
478
+
479
+ if next_level_nodes:
480
+ child_stats = _analyze_level(next_level_nodes, level + 1)
481
+ level_stats.update(child_stats)
482
+ else:
483
+ level_stats['levels'] = level + 1
484
+
485
+ return level_stats
486
+
487
+ stats = _analyze_level([self._root], 0)
488
+
489
+ # Calculate fill factor
490
+ total_capacity = self._total_nodes * self.order
491
+ fill_factor = self._size / max(1, total_capacity)
492
+
493
+ return {
494
+ 'size': self._size,
495
+ 'height': self._height,
496
+ 'total_nodes': self._total_nodes,
497
+ 'total_splits': self._total_splits,
498
+ 'order': self.order,
499
+ 'fill_factor': fill_factor,
500
+ 'levels': stats.get('levels', 0),
501
+ 'first_key': self.first_key(),
502
+ 'last_key': self.last_key()
503
+ }
504
+
505
+ # ============================================================================
506
+ # PERFORMANCE CHARACTERISTICS
507
+ # ============================================================================
508
+
509
+ @property
510
+ def backend_info(self) -> Dict[str, Any]:
511
+ """Get backend implementation info."""
512
+ return {
513
+ 'strategy': 'B_PLUS_TREE',
514
+ 'backend': 'Database-optimized B+ tree with leaf linking',
515
+ 'order': self.order,
516
+ 'case_sensitive': self.case_sensitive,
517
+ 'complexity': {
518
+ 'insert': f'O(log_{self.order} n)',
519
+ 'search': f'O(log_{self.order} n)',
520
+ 'delete': f'O(log_{self.order} n)',
521
+ 'range_query': f'O(log_{self.order} n + k)', # k = result size
522
+ 'sequential_access': 'O(n)', # Via leaf linking
523
+ 'space': 'O(n)',
524
+ 'disk_friendly': 'Optimized for page-based storage'
525
+ }
526
+ }
527
+
528
+ @property
529
+ def metrics(self) -> Dict[str, Any]:
530
+ """Get performance metrics."""
531
+ stats = self.get_tree_statistics()
532
+
533
+ return {
534
+ 'size': stats['size'],
535
+ 'height': stats['height'],
536
+ 'total_nodes': stats['total_nodes'],
537
+ 'order': stats['order'],
538
+ 'fill_factor': f"{stats['fill_factor'] * 100:.1f}%",
539
+ 'total_splits': stats['total_splits'],
540
+ 'first_key': str(stats['first_key']) if stats['first_key'] else 'None',
541
+ 'memory_usage': f"{stats['total_nodes'] * self.order * 20} bytes (estimated)"
542
+ }