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,316 @@
|
|
1
|
+
#exonware\xnode\strategies\impls\node_skip_list.py
|
2
|
+
"""
|
3
|
+
Skip List Node Strategy Implementation
|
4
|
+
|
5
|
+
This module implements the SKIP_LIST strategy for probabilistic data structures
|
6
|
+
with O(log n) expected performance for search, insertion, and deletion.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import random
|
10
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
11
|
+
from .base import ANodeTreeStrategy
|
12
|
+
from ...types import NodeMode, NodeTrait
|
13
|
+
|
14
|
+
|
15
|
+
class SkipListNode:
|
16
|
+
"""Node in the skip list."""
|
17
|
+
|
18
|
+
def __init__(self, key: str, value: Any = None, level: int = 0):
|
19
|
+
self.key = key
|
20
|
+
self.value = value
|
21
|
+
self.level = level
|
22
|
+
self.forward: List[Optional['SkipListNode']] = [None] * (level + 1)
|
23
|
+
self._hash = None
|
24
|
+
|
25
|
+
def __hash__(self) -> int:
|
26
|
+
"""Cache hash for performance."""
|
27
|
+
if self._hash is None:
|
28
|
+
self._hash = hash((self.key, self.value))
|
29
|
+
return self._hash
|
30
|
+
|
31
|
+
def __eq__(self, other) -> bool:
|
32
|
+
"""Structural equality."""
|
33
|
+
if not isinstance(other, SkipListNode):
|
34
|
+
return False
|
35
|
+
return self.key == other.key and self.value == other.value
|
36
|
+
|
37
|
+
|
38
|
+
class SkipListStrategy(ANodeTreeStrategy):
|
39
|
+
"""
|
40
|
+
Skip list node strategy for probabilistic data structures.
|
41
|
+
|
42
|
+
Provides O(log n) expected performance for search, insertion, and deletion
|
43
|
+
with simple implementation and good concurrent access properties.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
47
|
+
"""Initialize the skip list strategy."""
|
48
|
+
super().__init__(NodeMode.SKIP_LIST, traits, **options)
|
49
|
+
|
50
|
+
self.case_sensitive = options.get('case_sensitive', True)
|
51
|
+
self.max_level = options.get('max_level', 16) # Maximum level for skip list
|
52
|
+
self.probability = options.get('probability', 0.5) # Probability for level promotion
|
53
|
+
|
54
|
+
# Core skip list
|
55
|
+
self._header = SkipListNode("", None, self.max_level) # Header node
|
56
|
+
self._level = 0 # Current maximum level
|
57
|
+
self._size = 0
|
58
|
+
|
59
|
+
# Statistics
|
60
|
+
self._total_insertions = 0
|
61
|
+
self._total_deletions = 0
|
62
|
+
self._total_searches = 0
|
63
|
+
self._max_level_reached = 0
|
64
|
+
|
65
|
+
def get_supported_traits(self) -> NodeTrait:
|
66
|
+
"""Get the traits supported by the skip list strategy."""
|
67
|
+
return (NodeTrait.ORDERED | NodeTrait.INDEXED)
|
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 _random_level(self) -> int:
|
74
|
+
"""Generate random level for new node."""
|
75
|
+
level = 0
|
76
|
+
while random.random() < self.probability and level < self.max_level:
|
77
|
+
level += 1
|
78
|
+
self._max_level_reached = max(self._max_level_reached, level)
|
79
|
+
return level
|
80
|
+
|
81
|
+
def _search_path(self, key: str) -> List[Optional[SkipListNode]]:
|
82
|
+
"""Find the path to the node with given key."""
|
83
|
+
normalized_key = self._normalize_key(key)
|
84
|
+
current = self._header
|
85
|
+
path = [None] * (self._level + 1)
|
86
|
+
|
87
|
+
# Start from highest level and work down
|
88
|
+
for i in range(self._level, -1, -1):
|
89
|
+
while (current.forward[i] is not None and
|
90
|
+
self._normalize_key(current.forward[i].key) < normalized_key):
|
91
|
+
current = current.forward[i]
|
92
|
+
path[i] = current
|
93
|
+
|
94
|
+
return path
|
95
|
+
|
96
|
+
def _find_node(self, key: str) -> Optional[SkipListNode]:
|
97
|
+
"""Find node with given key."""
|
98
|
+
normalized_key = self._normalize_key(key)
|
99
|
+
current = self._header
|
100
|
+
|
101
|
+
# Start from highest level and work down
|
102
|
+
for i in range(self._level, -1, -1):
|
103
|
+
while (current.forward[i] is not None and
|
104
|
+
self._normalize_key(current.forward[i].key) < normalized_key):
|
105
|
+
current = current.forward[i]
|
106
|
+
|
107
|
+
# Check if we found the exact key
|
108
|
+
current = current.forward[0]
|
109
|
+
if current is not None and self._normalize_key(current.key) == normalized_key:
|
110
|
+
return current
|
111
|
+
|
112
|
+
return None
|
113
|
+
|
114
|
+
def _insert_node(self, key: str, value: Any) -> bool:
|
115
|
+
"""Insert node with given key and value."""
|
116
|
+
normalized_key = self._normalize_key(key)
|
117
|
+
|
118
|
+
# Find the path to insertion point
|
119
|
+
path = self._search_path(key)
|
120
|
+
current = path[0].forward[0]
|
121
|
+
|
122
|
+
# Check if key already exists
|
123
|
+
if current is not None and self._normalize_key(current.key) == normalized_key:
|
124
|
+
# Update existing node
|
125
|
+
current.value = value
|
126
|
+
return False
|
127
|
+
|
128
|
+
# Create new node
|
129
|
+
new_level = self._random_level()
|
130
|
+
new_node = SkipListNode(key, value, new_level)
|
131
|
+
|
132
|
+
# If new level is higher than current level, update header
|
133
|
+
if new_level > self._level:
|
134
|
+
for i in range(self._level + 1, new_level + 1):
|
135
|
+
path.append(self._header)
|
136
|
+
self._level = new_level
|
137
|
+
|
138
|
+
# Insert node into skip list
|
139
|
+
for i in range(new_level + 1):
|
140
|
+
new_node.forward[i] = path[i].forward[i]
|
141
|
+
path[i].forward[i] = new_node
|
142
|
+
|
143
|
+
self._size += 1
|
144
|
+
self._total_insertions += 1
|
145
|
+
return True
|
146
|
+
|
147
|
+
def _delete_node(self, key: str) -> bool:
|
148
|
+
"""Delete node with given key."""
|
149
|
+
normalized_key = self._normalize_key(key)
|
150
|
+
|
151
|
+
# Find the path to deletion point
|
152
|
+
path = self._search_path(key)
|
153
|
+
current = path[0].forward[0]
|
154
|
+
|
155
|
+
# Check if key exists
|
156
|
+
if current is None or self._normalize_key(current.key) != normalized_key:
|
157
|
+
return False
|
158
|
+
|
159
|
+
# Remove node from skip list
|
160
|
+
for i in range(self._level + 1):
|
161
|
+
if path[i].forward[i] != current:
|
162
|
+
break
|
163
|
+
path[i].forward[i] = current.forward[i]
|
164
|
+
|
165
|
+
# Update level if necessary
|
166
|
+
while (self._level > 0 and
|
167
|
+
self._header.forward[self._level] is None):
|
168
|
+
self._level -= 1
|
169
|
+
|
170
|
+
self._size -= 1
|
171
|
+
self._total_deletions += 1
|
172
|
+
return True
|
173
|
+
|
174
|
+
def _inorder_traversal(self) -> Iterator[Tuple[str, Any]]:
|
175
|
+
"""In-order traversal of skip list."""
|
176
|
+
current = self._header.forward[0]
|
177
|
+
while current is not None:
|
178
|
+
yield (current.key, current.value)
|
179
|
+
current = current.forward[0]
|
180
|
+
|
181
|
+
# ============================================================================
|
182
|
+
# CORE OPERATIONS
|
183
|
+
# ============================================================================
|
184
|
+
|
185
|
+
def put(self, key: Any, value: Any = None) -> None:
|
186
|
+
"""Store a key-value pair."""
|
187
|
+
if not isinstance(key, str):
|
188
|
+
key = str(key)
|
189
|
+
|
190
|
+
self._insert_node(key, value)
|
191
|
+
|
192
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
193
|
+
"""Retrieve a value by key."""
|
194
|
+
if not isinstance(key, str):
|
195
|
+
key = str(key)
|
196
|
+
|
197
|
+
self._total_searches += 1
|
198
|
+
node = self._find_node(key)
|
199
|
+
return node.value if node else default
|
200
|
+
|
201
|
+
def delete(self, key: Any) -> bool:
|
202
|
+
"""Remove a key-value pair."""
|
203
|
+
if not isinstance(key, str):
|
204
|
+
key = str(key)
|
205
|
+
|
206
|
+
return self._delete_node(key)
|
207
|
+
|
208
|
+
def has(self, key: Any) -> bool:
|
209
|
+
"""Check if key exists."""
|
210
|
+
if not isinstance(key, str):
|
211
|
+
key = str(key)
|
212
|
+
|
213
|
+
return self._find_node(key) is not None
|
214
|
+
|
215
|
+
def clear(self) -> None:
|
216
|
+
"""Clear all data."""
|
217
|
+
self._header = SkipListNode("", None, self.max_level)
|
218
|
+
self._level = 0
|
219
|
+
self._size = 0
|
220
|
+
|
221
|
+
def size(self) -> int:
|
222
|
+
"""Get number of key-value pairs."""
|
223
|
+
return self._size
|
224
|
+
|
225
|
+
def is_empty(self) -> bool:
|
226
|
+
"""Check if skip list is empty."""
|
227
|
+
return self._size == 0
|
228
|
+
|
229
|
+
# ============================================================================
|
230
|
+
# ITERATION
|
231
|
+
# ============================================================================
|
232
|
+
|
233
|
+
def keys(self) -> Iterator[str]:
|
234
|
+
"""Iterate over keys in sorted order."""
|
235
|
+
for key, _ in self._inorder_traversal():
|
236
|
+
yield key
|
237
|
+
|
238
|
+
def values(self) -> Iterator[Any]:
|
239
|
+
"""Iterate over values in key order."""
|
240
|
+
for _, value in self._inorder_traversal():
|
241
|
+
yield value
|
242
|
+
|
243
|
+
def items(self) -> Iterator[Tuple[str, Any]]:
|
244
|
+
"""Iterate over key-value pairs in sorted order."""
|
245
|
+
yield from self._inorder_traversal()
|
246
|
+
|
247
|
+
def __iter__(self) -> Iterator[str]:
|
248
|
+
"""Iterate over keys."""
|
249
|
+
yield from self.keys()
|
250
|
+
|
251
|
+
# ============================================================================
|
252
|
+
# SKIP LIST SPECIFIC OPERATIONS
|
253
|
+
# ============================================================================
|
254
|
+
|
255
|
+
def get_min(self) -> Optional[Tuple[str, Any]]:
|
256
|
+
"""Get the minimum key-value pair."""
|
257
|
+
if self._size == 0:
|
258
|
+
return None
|
259
|
+
|
260
|
+
current = self._header.forward[0]
|
261
|
+
return (current.key, current.value) if current else None
|
262
|
+
|
263
|
+
def get_max(self) -> Optional[Tuple[str, Any]]:
|
264
|
+
"""Get the maximum key-value pair."""
|
265
|
+
if self._size == 0:
|
266
|
+
return None
|
267
|
+
|
268
|
+
current = self._header
|
269
|
+
for i in range(self._level, -1, -1):
|
270
|
+
while current.forward[i] is not None:
|
271
|
+
current = current.forward[i]
|
272
|
+
|
273
|
+
return (current.key, current.value) if current != self._header else None
|
274
|
+
|
275
|
+
def range_query(self, start_key: str, end_key: str) -> Iterator[Tuple[str, Any]]:
|
276
|
+
"""Get all key-value pairs in range [start_key, end_key]."""
|
277
|
+
if not isinstance(start_key, str) or not isinstance(end_key, str):
|
278
|
+
return
|
279
|
+
|
280
|
+
normalized_start = self._normalize_key(start_key)
|
281
|
+
normalized_end = self._normalize_key(end_key)
|
282
|
+
|
283
|
+
# Find starting position
|
284
|
+
path = self._search_path(start_key)
|
285
|
+
current = path[0].forward[0]
|
286
|
+
|
287
|
+
# Iterate through range
|
288
|
+
while (current is not None and
|
289
|
+
self._normalize_key(current.key) <= normalized_end):
|
290
|
+
if self._normalize_key(current.key) >= normalized_start:
|
291
|
+
yield (current.key, current.value)
|
292
|
+
current = current.forward[0]
|
293
|
+
|
294
|
+
def get_level(self) -> int:
|
295
|
+
"""Get current maximum level."""
|
296
|
+
return self._level
|
297
|
+
|
298
|
+
def get_max_level(self) -> int:
|
299
|
+
"""Get maximum level reached."""
|
300
|
+
return self._max_level_reached
|
301
|
+
|
302
|
+
def get_stats(self) -> Dict[str, Any]:
|
303
|
+
"""Get performance statistics."""
|
304
|
+
return {
|
305
|
+
'size': self._size,
|
306
|
+
'level': self._level,
|
307
|
+
'max_level_reached': self._max_level_reached,
|
308
|
+
'total_insertions': self._total_insertions,
|
309
|
+
'total_deletions': self._total_deletions,
|
310
|
+
'total_searches': self._total_searches,
|
311
|
+
'probability': self.probability,
|
312
|
+
'max_level': self.max_level,
|
313
|
+
'strategy': 'SKIP_LIST',
|
314
|
+
'backend': 'Probabilistic skip list with O(log n) expected performance',
|
315
|
+
'traits': [trait.name for trait in NodeTrait if self.has_trait(trait)]
|
316
|
+
}
|
@@ -0,0 +1,393 @@
|
|
1
|
+
#exonware\xnode\strategies\impls\node_splay_tree.py
|
2
|
+
"""
|
3
|
+
Splay Tree Node Strategy Implementation
|
4
|
+
|
5
|
+
This module implements the SPLAY_TREE strategy for self-adjusting binary
|
6
|
+
search trees with amortized O(log n) performance.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
10
|
+
from .base import ANodeTreeStrategy
|
11
|
+
from ...types import NodeMode, NodeTrait
|
12
|
+
|
13
|
+
|
14
|
+
class SplayTreeNode:
|
15
|
+
"""Node in the splay tree."""
|
16
|
+
|
17
|
+
def __init__(self, key: str, value: Any = None):
|
18
|
+
self.key = key
|
19
|
+
self.value = value
|
20
|
+
self.left: Optional['SplayTreeNode'] = None
|
21
|
+
self.right: Optional['SplayTreeNode'] = None
|
22
|
+
self.parent: Optional['SplayTreeNode'] = None
|
23
|
+
self._hash = None
|
24
|
+
|
25
|
+
def __hash__(self) -> int:
|
26
|
+
"""Cache hash for performance."""
|
27
|
+
if self._hash is None:
|
28
|
+
self._hash = hash((self.key, self.value))
|
29
|
+
return self._hash
|
30
|
+
|
31
|
+
def __eq__(self, other) -> bool:
|
32
|
+
"""Structural equality."""
|
33
|
+
if not isinstance(other, SplayTreeNode):
|
34
|
+
return False
|
35
|
+
return self.key == other.key and self.value == other.value
|
36
|
+
|
37
|
+
|
38
|
+
class SplayTreeStrategy(ANodeTreeStrategy):
|
39
|
+
"""
|
40
|
+
Splay tree node strategy for self-adjusting binary search trees.
|
41
|
+
|
42
|
+
Provides amortized O(log n) performance by moving accessed nodes
|
43
|
+
to the root through splaying operations.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
47
|
+
"""Initialize the splay tree strategy."""
|
48
|
+
super().__init__(NodeMode.SPLAY_TREE, traits, **options)
|
49
|
+
|
50
|
+
self.case_sensitive = options.get('case_sensitive', True)
|
51
|
+
|
52
|
+
# Core splay tree
|
53
|
+
self._root: Optional[SplayTreeNode] = None
|
54
|
+
self._size = 0
|
55
|
+
|
56
|
+
# Statistics
|
57
|
+
self._total_insertions = 0
|
58
|
+
self._total_deletions = 0
|
59
|
+
self._total_splays = 0
|
60
|
+
self._max_height = 0
|
61
|
+
|
62
|
+
def get_supported_traits(self) -> NodeTrait:
|
63
|
+
"""Get the traits supported by the splay tree strategy."""
|
64
|
+
return (NodeTrait.ORDERED | NodeTrait.INDEXED)
|
65
|
+
|
66
|
+
def _normalize_key(self, key: str) -> str:
|
67
|
+
"""Normalize key based on case sensitivity."""
|
68
|
+
return key if self.case_sensitive else key.lower()
|
69
|
+
|
70
|
+
def _get_height(self, node: Optional[SplayTreeNode]) -> int:
|
71
|
+
"""Get height of node."""
|
72
|
+
if not node:
|
73
|
+
return 0
|
74
|
+
|
75
|
+
left_height = self._get_height(node.left)
|
76
|
+
right_height = self._get_height(node.right)
|
77
|
+
return 1 + max(left_height, right_height)
|
78
|
+
|
79
|
+
def _rotate_right(self, node: SplayTreeNode) -> None:
|
80
|
+
"""Right rotation around node."""
|
81
|
+
left_child = node.left
|
82
|
+
if not left_child:
|
83
|
+
return
|
84
|
+
|
85
|
+
# Update parent connections
|
86
|
+
node.left = left_child.right
|
87
|
+
if left_child.right:
|
88
|
+
left_child.right.parent = node
|
89
|
+
|
90
|
+
left_child.parent = node.parent
|
91
|
+
if not node.parent:
|
92
|
+
self._root = left_child
|
93
|
+
elif node == node.parent.left:
|
94
|
+
node.parent.left = left_child
|
95
|
+
else:
|
96
|
+
node.parent.right = left_child
|
97
|
+
|
98
|
+
# Update rotation
|
99
|
+
left_child.right = node
|
100
|
+
node.parent = left_child
|
101
|
+
|
102
|
+
def _rotate_left(self, node: SplayTreeNode) -> None:
|
103
|
+
"""Left rotation around node."""
|
104
|
+
right_child = node.right
|
105
|
+
if not right_child:
|
106
|
+
return
|
107
|
+
|
108
|
+
# Update parent connections
|
109
|
+
node.right = right_child.left
|
110
|
+
if right_child.left:
|
111
|
+
right_child.left.parent = node
|
112
|
+
|
113
|
+
right_child.parent = node.parent
|
114
|
+
if not node.parent:
|
115
|
+
self._root = right_child
|
116
|
+
elif node == node.parent.right:
|
117
|
+
node.parent.right = right_child
|
118
|
+
else:
|
119
|
+
node.parent.left = right_child
|
120
|
+
|
121
|
+
# Update rotation
|
122
|
+
right_child.left = node
|
123
|
+
node.parent = right_child
|
124
|
+
|
125
|
+
def _splay(self, node: SplayTreeNode) -> None:
|
126
|
+
"""Splay node to root."""
|
127
|
+
while node.parent:
|
128
|
+
parent = node.parent
|
129
|
+
grandparent = parent.parent
|
130
|
+
|
131
|
+
if not grandparent:
|
132
|
+
# Zig case
|
133
|
+
if node == parent.left:
|
134
|
+
self._rotate_right(parent)
|
135
|
+
else:
|
136
|
+
self._rotate_left(parent)
|
137
|
+
elif node == parent.left and parent == grandparent.left:
|
138
|
+
# Zig-zig case (left-left)
|
139
|
+
self._rotate_right(grandparent)
|
140
|
+
self._rotate_right(parent)
|
141
|
+
elif node == parent.right and parent == grandparent.right:
|
142
|
+
# Zig-zig case (right-right)
|
143
|
+
self._rotate_left(grandparent)
|
144
|
+
self._rotate_left(parent)
|
145
|
+
elif node == parent.right and parent == grandparent.left:
|
146
|
+
# Zig-zag case (left-right)
|
147
|
+
self._rotate_left(parent)
|
148
|
+
self._rotate_right(grandparent)
|
149
|
+
else:
|
150
|
+
# Zig-zag case (right-left)
|
151
|
+
self._rotate_right(parent)
|
152
|
+
self._rotate_left(grandparent)
|
153
|
+
|
154
|
+
self._total_splays += 1
|
155
|
+
|
156
|
+
def _find_node(self, key: str) -> Optional[SplayTreeNode]:
|
157
|
+
"""Find node with given key and splay it to root."""
|
158
|
+
normalized_key = self._normalize_key(key)
|
159
|
+
current = self._root
|
160
|
+
|
161
|
+
while current:
|
162
|
+
current_key = self._normalize_key(current.key)
|
163
|
+
if normalized_key < current_key:
|
164
|
+
current = current.left
|
165
|
+
elif normalized_key > current_key:
|
166
|
+
current = current.right
|
167
|
+
else:
|
168
|
+
# Found the node, splay it to root
|
169
|
+
self._splay(current)
|
170
|
+
return current
|
171
|
+
|
172
|
+
return None
|
173
|
+
|
174
|
+
def _insert_node(self, key: str, value: Any) -> bool:
|
175
|
+
"""Insert node with given key and value."""
|
176
|
+
normalized_key = self._normalize_key(key)
|
177
|
+
|
178
|
+
# Create new node
|
179
|
+
new_node = SplayTreeNode(key, value)
|
180
|
+
|
181
|
+
# Find insertion point
|
182
|
+
current = self._root
|
183
|
+
parent = None
|
184
|
+
|
185
|
+
while current:
|
186
|
+
parent = current
|
187
|
+
current_key = self._normalize_key(current.key)
|
188
|
+
if normalized_key < current_key:
|
189
|
+
current = current.left
|
190
|
+
elif normalized_key > current_key:
|
191
|
+
current = current.right
|
192
|
+
else:
|
193
|
+
# Key already exists, update value and splay
|
194
|
+
current.value = value
|
195
|
+
self._splay(current)
|
196
|
+
return False
|
197
|
+
|
198
|
+
# Insert new node
|
199
|
+
new_node.parent = parent
|
200
|
+
if not parent:
|
201
|
+
self._root = new_node
|
202
|
+
elif normalized_key < self._normalize_key(parent.key):
|
203
|
+
parent.left = new_node
|
204
|
+
else:
|
205
|
+
parent.right = new_node
|
206
|
+
|
207
|
+
# Splay new node to root
|
208
|
+
self._splay(new_node)
|
209
|
+
|
210
|
+
self._size += 1
|
211
|
+
self._total_insertions += 1
|
212
|
+
self._max_height = max(self._max_height, self._get_height(self._root))
|
213
|
+
return True
|
214
|
+
|
215
|
+
def _delete_node(self, key: str) -> bool:
|
216
|
+
"""Delete node with given key."""
|
217
|
+
node = self._find_node(key)
|
218
|
+
if not node:
|
219
|
+
return False
|
220
|
+
|
221
|
+
# Splay node to root
|
222
|
+
self._splay(node)
|
223
|
+
|
224
|
+
# If node has no children, just remove it
|
225
|
+
if not node.left and not node.right:
|
226
|
+
self._root = None
|
227
|
+
elif not node.left:
|
228
|
+
# Only right child
|
229
|
+
self._root = node.right
|
230
|
+
self._root.parent = None
|
231
|
+
elif not node.right:
|
232
|
+
# Only left child
|
233
|
+
self._root = node.left
|
234
|
+
self._root.parent = None
|
235
|
+
else:
|
236
|
+
# Both children exist
|
237
|
+
# Find maximum in left subtree
|
238
|
+
max_left = node.left
|
239
|
+
while max_left.right:
|
240
|
+
max_left = max_left.right
|
241
|
+
|
242
|
+
# Splay max_left to root of left subtree
|
243
|
+
self._splay(max_left)
|
244
|
+
|
245
|
+
# Attach right subtree to max_left
|
246
|
+
max_left.right = node.right
|
247
|
+
if node.right:
|
248
|
+
node.right.parent = max_left
|
249
|
+
|
250
|
+
# Make max_left the new root
|
251
|
+
self._root = max_left
|
252
|
+
self._root.parent = None
|
253
|
+
|
254
|
+
self._size -= 1
|
255
|
+
self._total_deletions += 1
|
256
|
+
return True
|
257
|
+
|
258
|
+
def _inorder_traversal(self, node: Optional[SplayTreeNode]) -> Iterator[Tuple[str, Any]]:
|
259
|
+
"""In-order traversal of tree."""
|
260
|
+
if node:
|
261
|
+
yield from self._inorder_traversal(node.left)
|
262
|
+
yield (node.key, node.value)
|
263
|
+
yield from self._inorder_traversal(node.right)
|
264
|
+
|
265
|
+
# ============================================================================
|
266
|
+
# CORE OPERATIONS
|
267
|
+
# ============================================================================
|
268
|
+
|
269
|
+
def put(self, key: Any, value: Any = None) -> None:
|
270
|
+
"""Store a key-value pair."""
|
271
|
+
if not isinstance(key, str):
|
272
|
+
key = str(key)
|
273
|
+
|
274
|
+
self._insert_node(key, value)
|
275
|
+
|
276
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
277
|
+
"""Retrieve a value by key."""
|
278
|
+
if not isinstance(key, str):
|
279
|
+
key = str(key)
|
280
|
+
|
281
|
+
node = self._find_node(key)
|
282
|
+
return node.value if node else default
|
283
|
+
|
284
|
+
def delete(self, key: Any) -> bool:
|
285
|
+
"""Remove a key-value pair."""
|
286
|
+
if not isinstance(key, str):
|
287
|
+
key = str(key)
|
288
|
+
|
289
|
+
return self._delete_node(key)
|
290
|
+
|
291
|
+
def has(self, key: Any) -> bool:
|
292
|
+
"""Check if key exists."""
|
293
|
+
if not isinstance(key, str):
|
294
|
+
key = str(key)
|
295
|
+
|
296
|
+
return self._find_node(key) is not None
|
297
|
+
|
298
|
+
def clear(self) -> None:
|
299
|
+
"""Clear all data."""
|
300
|
+
self._root = None
|
301
|
+
self._size = 0
|
302
|
+
|
303
|
+
def size(self) -> int:
|
304
|
+
"""Get number of key-value pairs."""
|
305
|
+
return self._size
|
306
|
+
|
307
|
+
def is_empty(self) -> bool:
|
308
|
+
"""Check if tree is empty."""
|
309
|
+
return self._root is None
|
310
|
+
|
311
|
+
# ============================================================================
|
312
|
+
# ITERATION
|
313
|
+
# ============================================================================
|
314
|
+
|
315
|
+
def keys(self) -> Iterator[str]:
|
316
|
+
"""Iterate over keys in sorted order."""
|
317
|
+
for key, _ in self._inorder_traversal(self._root):
|
318
|
+
yield key
|
319
|
+
|
320
|
+
def values(self) -> Iterator[Any]:
|
321
|
+
"""Iterate over values in key order."""
|
322
|
+
for _, value in self._inorder_traversal(self._root):
|
323
|
+
yield value
|
324
|
+
|
325
|
+
def items(self) -> Iterator[Tuple[str, Any]]:
|
326
|
+
"""Iterate over key-value pairs in sorted order."""
|
327
|
+
yield from self._inorder_traversal(self._root)
|
328
|
+
|
329
|
+
def __iter__(self) -> Iterator[str]:
|
330
|
+
"""Iterate over keys."""
|
331
|
+
yield from self.keys()
|
332
|
+
|
333
|
+
# ============================================================================
|
334
|
+
# SPLAY TREE SPECIFIC OPERATIONS
|
335
|
+
# ============================================================================
|
336
|
+
|
337
|
+
def get_min(self) -> Optional[Tuple[str, Any]]:
|
338
|
+
"""Get the minimum key-value pair."""
|
339
|
+
if not self._root:
|
340
|
+
return None
|
341
|
+
|
342
|
+
# Find minimum and splay it to root
|
343
|
+
current = self._root
|
344
|
+
while current.left:
|
345
|
+
current = current.left
|
346
|
+
|
347
|
+
self._splay(current)
|
348
|
+
return (current.key, current.value)
|
349
|
+
|
350
|
+
def get_max(self) -> Optional[Tuple[str, Any]]:
|
351
|
+
"""Get the maximum key-value pair."""
|
352
|
+
if not self._root:
|
353
|
+
return None
|
354
|
+
|
355
|
+
# Find maximum and splay it to root
|
356
|
+
current = self._root
|
357
|
+
while current.right:
|
358
|
+
current = current.right
|
359
|
+
|
360
|
+
self._splay(current)
|
361
|
+
return (current.key, current.value)
|
362
|
+
|
363
|
+
def get_height(self) -> int:
|
364
|
+
"""Get the height of the tree."""
|
365
|
+
return self._get_height(self._root)
|
366
|
+
|
367
|
+
def splay_to_root(self, key: str) -> bool:
|
368
|
+
"""Splay node with given key to root."""
|
369
|
+
node = self._find_node(key)
|
370
|
+
return node is not None
|
371
|
+
|
372
|
+
def get_root_key(self) -> Optional[str]:
|
373
|
+
"""Get the key of the root node."""
|
374
|
+
return self._root.key if self._root else None
|
375
|
+
|
376
|
+
def get_root_value(self) -> Optional[Any]:
|
377
|
+
"""Get the value of the root node."""
|
378
|
+
return self._root.value if self._root else None
|
379
|
+
|
380
|
+
def get_stats(self) -> Dict[str, Any]:
|
381
|
+
"""Get performance statistics."""
|
382
|
+
return {
|
383
|
+
'size': self._size,
|
384
|
+
'height': self._get_height(self._root),
|
385
|
+
'max_height': self._max_height,
|
386
|
+
'total_insertions': self._total_insertions,
|
387
|
+
'total_deletions': self._total_deletions,
|
388
|
+
'total_splays': self._total_splays,
|
389
|
+
'root_key': self.get_root_key(),
|
390
|
+
'strategy': 'SPLAY_TREE',
|
391
|
+
'backend': 'Self-adjusting splay tree with amortized O(log n) performance',
|
392
|
+
'traits': [trait.name for trait in NodeTrait if self.has_trait(trait)]
|
393
|
+
}
|