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