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,289 @@
|
|
1
|
+
"""
|
2
|
+
Segment Tree Node Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the SEGMENT_TREE strategy for range queries
|
5
|
+
and updates with O(log n) complexity.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, List, Optional, Callable, Dict, Union
|
9
|
+
from .base import ANodeTreeStrategy
|
10
|
+
from ...types import NodeMode, NodeTrait
|
11
|
+
|
12
|
+
|
13
|
+
class SegmentTreeStrategy(ANodeTreeStrategy):
|
14
|
+
"""
|
15
|
+
Segment Tree node strategy for efficient range queries and updates.
|
16
|
+
|
17
|
+
Provides O(log n) range queries and updates for associative operations
|
18
|
+
like sum, min, max, etc.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
22
|
+
"""Initialize the Segment Tree strategy."""
|
23
|
+
super().__init__(NodeMode.SEGMENT_TREE, traits, **options)
|
24
|
+
|
25
|
+
self._size = options.get('initial_size', 0)
|
26
|
+
self._operation = options.get('operation', 'sum') # sum, min, max, etc.
|
27
|
+
self._identity = self._get_identity(self._operation)
|
28
|
+
self._combiner = self._get_combiner(self._operation)
|
29
|
+
|
30
|
+
# Internal tree representation (0-indexed, 1-based tree)
|
31
|
+
self._tree: List[Any] = [self._identity] * (4 * max(1, self._size))
|
32
|
+
self._values: Dict[str, Any] = {} # Key-value storage for compatibility
|
33
|
+
self._indices: Dict[str, int] = {} # Map keys to tree indices
|
34
|
+
self._reverse_indices: Dict[int, str] = {} # Map indices to keys
|
35
|
+
self._next_index = 0
|
36
|
+
|
37
|
+
def get_supported_traits(self) -> NodeTrait:
|
38
|
+
"""Get the traits supported by the segment tree strategy."""
|
39
|
+
return (NodeTrait.INDEXED | NodeTrait.HIERARCHICAL | NodeTrait.STREAMING)
|
40
|
+
|
41
|
+
def _get_identity(self, operation: str) -> Any:
|
42
|
+
"""Get identity element for the operation."""
|
43
|
+
identities = {
|
44
|
+
'sum': 0,
|
45
|
+
'min': float('inf'),
|
46
|
+
'max': float('-inf'),
|
47
|
+
'product': 1,
|
48
|
+
'gcd': 0,
|
49
|
+
'lcm': 1,
|
50
|
+
'xor': 0,
|
51
|
+
'and': True,
|
52
|
+
'or': False
|
53
|
+
}
|
54
|
+
return identities.get(operation, 0)
|
55
|
+
|
56
|
+
def _get_combiner(self, operation: str) -> Callable[[Any, Any], Any]:
|
57
|
+
"""Get combiner function for the operation."""
|
58
|
+
import math
|
59
|
+
|
60
|
+
combiners = {
|
61
|
+
'sum': lambda a, b: a + b,
|
62
|
+
'min': lambda a, b: min(a, b),
|
63
|
+
'max': lambda a, b: max(a, b),
|
64
|
+
'product': lambda a, b: a * b,
|
65
|
+
'gcd': lambda a, b: math.gcd(int(a), int(b)),
|
66
|
+
'lcm': lambda a, b: abs(int(a) * int(b)) // math.gcd(int(a), int(b)),
|
67
|
+
'xor': lambda a, b: a ^ b,
|
68
|
+
'and': lambda a, b: a and b,
|
69
|
+
'or': lambda a, b: a or b
|
70
|
+
}
|
71
|
+
return combiners.get(operation, lambda a, b: a + b)
|
72
|
+
|
73
|
+
# ============================================================================
|
74
|
+
# CORE OPERATIONS (Key-based interface for compatibility)
|
75
|
+
# ============================================================================
|
76
|
+
|
77
|
+
def put(self, key: Any, value: Any = None) -> None:
|
78
|
+
"""Store a value at the given key."""
|
79
|
+
key_str = str(key)
|
80
|
+
|
81
|
+
# Convert value to numeric if possible
|
82
|
+
try:
|
83
|
+
numeric_value = float(value) if value is not None else 0.0
|
84
|
+
except (ValueError, TypeError):
|
85
|
+
numeric_value = 0.0
|
86
|
+
|
87
|
+
if key_str in self._indices:
|
88
|
+
# Update existing
|
89
|
+
idx = self._indices[key_str]
|
90
|
+
self._update_point(idx, numeric_value)
|
91
|
+
else:
|
92
|
+
# Add new
|
93
|
+
if self._next_index >= len(self._tree) // 4:
|
94
|
+
self._resize_tree()
|
95
|
+
|
96
|
+
idx = self._next_index
|
97
|
+
self._indices[key_str] = idx
|
98
|
+
self._reverse_indices[idx] = key_str
|
99
|
+
self._next_index += 1
|
100
|
+
self._update_point(idx, numeric_value)
|
101
|
+
|
102
|
+
self._values[key_str] = value
|
103
|
+
|
104
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
105
|
+
"""Retrieve a value by key."""
|
106
|
+
key_str = str(key)
|
107
|
+
return self._values.get(key_str, default)
|
108
|
+
|
109
|
+
def has(self, key: Any) -> bool:
|
110
|
+
"""Check if key exists."""
|
111
|
+
return str(key) in self._values
|
112
|
+
|
113
|
+
def remove(self, key: Any) -> bool:
|
114
|
+
"""Remove value by key."""
|
115
|
+
key_str = str(key)
|
116
|
+
if key_str not in self._indices:
|
117
|
+
return False
|
118
|
+
|
119
|
+
idx = self._indices[key_str]
|
120
|
+
self._update_point(idx, self._identity)
|
121
|
+
|
122
|
+
del self._indices[key_str]
|
123
|
+
del self._reverse_indices[idx]
|
124
|
+
del self._values[key_str]
|
125
|
+
|
126
|
+
return True
|
127
|
+
|
128
|
+
def delete(self, key: Any) -> bool:
|
129
|
+
"""Remove value by key (alias for remove)."""
|
130
|
+
return self.remove(key)
|
131
|
+
|
132
|
+
def clear(self) -> None:
|
133
|
+
"""Clear all data."""
|
134
|
+
self._tree = [self._identity] * (4 * max(1, self._size))
|
135
|
+
self._values.clear()
|
136
|
+
self._indices.clear()
|
137
|
+
self._reverse_indices.clear()
|
138
|
+
self._next_index = 0
|
139
|
+
|
140
|
+
def keys(self) -> Iterator[str]:
|
141
|
+
"""Get all keys."""
|
142
|
+
return iter(self._values.keys())
|
143
|
+
|
144
|
+
def values(self) -> Iterator[Any]:
|
145
|
+
"""Get all values."""
|
146
|
+
return iter(self._values.values())
|
147
|
+
|
148
|
+
def items(self) -> Iterator[tuple[str, Any]]:
|
149
|
+
"""Get all key-value pairs."""
|
150
|
+
return iter(self._values.items())
|
151
|
+
|
152
|
+
def __len__(self) -> int:
|
153
|
+
"""Get the number of items."""
|
154
|
+
return len(self._values)
|
155
|
+
|
156
|
+
def to_native(self) -> Dict[str, Any]:
|
157
|
+
"""Convert to native Python dict."""
|
158
|
+
return dict(self._values)
|
159
|
+
|
160
|
+
@property
|
161
|
+
def is_list(self) -> bool:
|
162
|
+
"""This can behave like a list."""
|
163
|
+
return True
|
164
|
+
|
165
|
+
@property
|
166
|
+
def is_dict(self) -> bool:
|
167
|
+
"""This can behave like a dict."""
|
168
|
+
return True
|
169
|
+
|
170
|
+
# ============================================================================
|
171
|
+
# SEGMENT TREE SPECIFIC OPERATIONS
|
172
|
+
# ============================================================================
|
173
|
+
|
174
|
+
def _resize_tree(self) -> None:
|
175
|
+
"""Resize the internal tree when needed."""
|
176
|
+
old_size = len(self._tree)
|
177
|
+
new_size = old_size * 2
|
178
|
+
self._tree.extend([self._identity] * (new_size - old_size))
|
179
|
+
|
180
|
+
def _update_point(self, idx: int, value: Any) -> None:
|
181
|
+
"""Update a single point in the segment tree."""
|
182
|
+
# Convert to 1-based indexing for tree
|
183
|
+
tree_idx = idx + len(self._tree) // 4
|
184
|
+
|
185
|
+
# Ensure tree is large enough
|
186
|
+
while tree_idx >= len(self._tree):
|
187
|
+
self._resize_tree()
|
188
|
+
tree_idx = idx + len(self._tree) // 4
|
189
|
+
|
190
|
+
# Update leaf
|
191
|
+
self._tree[tree_idx] = value
|
192
|
+
|
193
|
+
# Update ancestors
|
194
|
+
while tree_idx > 1:
|
195
|
+
tree_idx //= 2
|
196
|
+
left_child = self._tree[tree_idx * 2] if tree_idx * 2 < len(self._tree) else self._identity
|
197
|
+
right_child = self._tree[tree_idx * 2 + 1] if tree_idx * 2 + 1 < len(self._tree) else self._identity
|
198
|
+
self._tree[tree_idx] = self._combiner(left_child, right_child)
|
199
|
+
|
200
|
+
def range_query(self, left: int, right: int) -> Any:
|
201
|
+
"""Query range [left, right] inclusive."""
|
202
|
+
return self._range_query_recursive(1, 0, len(self._tree) // 4 - 1, left, right)
|
203
|
+
|
204
|
+
def _range_query_recursive(self, node: int, start: int, end: int,
|
205
|
+
query_left: int, query_right: int) -> Any:
|
206
|
+
"""Recursive range query implementation."""
|
207
|
+
if query_right < start or query_left > end:
|
208
|
+
return self._identity
|
209
|
+
|
210
|
+
if query_left <= start and end <= query_right:
|
211
|
+
return self._tree[node] if node < len(self._tree) else self._identity
|
212
|
+
|
213
|
+
mid = (start + end) // 2
|
214
|
+
left_result = self._range_query_recursive(2 * node, start, mid, query_left, query_right)
|
215
|
+
right_result = self._range_query_recursive(2 * node + 1, mid + 1, end, query_left, query_right)
|
216
|
+
|
217
|
+
return self._combiner(left_result, right_result)
|
218
|
+
|
219
|
+
def range_update(self, left: int, right: int, value: Any) -> None:
|
220
|
+
"""Update range [left, right] with value (point updates)."""
|
221
|
+
for i in range(left, right + 1):
|
222
|
+
if i in self._reverse_indices:
|
223
|
+
key = self._reverse_indices[i]
|
224
|
+
self.put(key, value)
|
225
|
+
|
226
|
+
def prefix_query(self, index: int) -> Any:
|
227
|
+
"""Query prefix [0, index]."""
|
228
|
+
return self.range_query(0, index)
|
229
|
+
|
230
|
+
def suffix_query(self, index: int) -> Any:
|
231
|
+
"""Query suffix [index, size-1]."""
|
232
|
+
return self.range_query(index, len(self._values) - 1)
|
233
|
+
|
234
|
+
def find_first_greater(self, value: Any) -> Optional[int]:
|
235
|
+
"""Find first index where tree value > given value."""
|
236
|
+
for i in range(len(self._values)):
|
237
|
+
if i in self._reverse_indices:
|
238
|
+
key = self._reverse_indices[i]
|
239
|
+
if key in self._values:
|
240
|
+
try:
|
241
|
+
if float(self._values[key]) > float(value):
|
242
|
+
return i
|
243
|
+
except (ValueError, TypeError):
|
244
|
+
continue
|
245
|
+
return None
|
246
|
+
|
247
|
+
def get_operation_info(self) -> Dict[str, Any]:
|
248
|
+
"""Get information about the current operation."""
|
249
|
+
return {
|
250
|
+
'operation': self._operation,
|
251
|
+
'identity': self._identity,
|
252
|
+
'total_result': self.range_query(0, max(0, len(self._values) - 1))
|
253
|
+
}
|
254
|
+
|
255
|
+
# ============================================================================
|
256
|
+
# PERFORMANCE CHARACTERISTICS
|
257
|
+
# ============================================================================
|
258
|
+
|
259
|
+
@property
|
260
|
+
def backend_info(self) -> Dict[str, Any]:
|
261
|
+
"""Get backend implementation info."""
|
262
|
+
return {
|
263
|
+
'strategy': 'SEGMENT_TREE',
|
264
|
+
'backend': 'Array-based segment tree',
|
265
|
+
'operation': self._operation,
|
266
|
+
'complexity': {
|
267
|
+
'point_update': 'O(log n)',
|
268
|
+
'range_query': 'O(log n)',
|
269
|
+
'range_update': 'O(n log n)',
|
270
|
+
'build': 'O(n)'
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
@property
|
275
|
+
def metrics(self) -> Dict[str, Any]:
|
276
|
+
"""Get performance metrics."""
|
277
|
+
tree_height = 0
|
278
|
+
if len(self._values) > 0:
|
279
|
+
import math
|
280
|
+
tree_height = math.ceil(math.log2(len(self._values)))
|
281
|
+
|
282
|
+
return {
|
283
|
+
'size': len(self._values),
|
284
|
+
'tree_size': len(self._tree),
|
285
|
+
'tree_height': tree_height,
|
286
|
+
'operation': self._operation,
|
287
|
+
'memory_usage': f"{len(self._tree) * 8 + len(self._values) * 24} bytes (estimated)",
|
288
|
+
'utilization': f"{len(self._values) / max(1, len(self._tree) // 4) * 100:.1f}%"
|
289
|
+
}
|
@@ -0,0 +1,354 @@
|
|
1
|
+
"""
|
2
|
+
Hash Set Node Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the SET_HASH strategy for efficient set operations
|
5
|
+
with O(1) average-case membership testing and insertion.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, Set, Dict, Union, List
|
9
|
+
import hashlib
|
10
|
+
from ._base_node import aNodeStrategy
|
11
|
+
from ...types import NodeMode, NodeTrait
|
12
|
+
|
13
|
+
|
14
|
+
class xSetHashStrategy(aNodeStrategy):
|
15
|
+
"""
|
16
|
+
Hash Set node strategy for efficient set operations.
|
17
|
+
|
18
|
+
Provides O(1) average-case membership testing, insertion, and deletion
|
19
|
+
with automatic handling of duplicates and fast set operations.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
23
|
+
"""Initialize the Hash Set strategy."""
|
24
|
+
super().__init__(NodeMode.SET_HASH, traits, **options)
|
25
|
+
|
26
|
+
self.load_factor = options.get('load_factor', 0.75)
|
27
|
+
self.initial_capacity = options.get('initial_capacity', 16)
|
28
|
+
|
29
|
+
# Core storage: hash set for uniqueness
|
30
|
+
self._set: Set[str] = set()
|
31
|
+
self._values: Dict[str, Any] = {} # Value storage for compatibility
|
32
|
+
self._size = 0
|
33
|
+
|
34
|
+
# Set-specific options
|
35
|
+
self.case_sensitive = options.get('case_sensitive', True)
|
36
|
+
self.allow_none = options.get('allow_none', True)
|
37
|
+
|
38
|
+
def get_supported_traits(self) -> NodeTrait:
|
39
|
+
"""Get the traits supported by the hash set strategy."""
|
40
|
+
return (NodeTrait.INDEXED | NodeTrait.STREAMING)
|
41
|
+
|
42
|
+
# ============================================================================
|
43
|
+
# CORE OPERATIONS (Key-based interface for compatibility)
|
44
|
+
# ============================================================================
|
45
|
+
|
46
|
+
def put(self, key: Any, value: Any = None) -> None:
|
47
|
+
"""Add a value to the set (key becomes the set element)."""
|
48
|
+
if value is None and not self.allow_none:
|
49
|
+
return
|
50
|
+
|
51
|
+
# Use key as the set element
|
52
|
+
element = str(key) if not self.case_sensitive else str(key)
|
53
|
+
if not self.case_sensitive:
|
54
|
+
element = element.lower()
|
55
|
+
|
56
|
+
# Add to set (automatic deduplication)
|
57
|
+
was_new = element not in self._set
|
58
|
+
self._set.add(element)
|
59
|
+
self._values[element] = value if value is not None else key
|
60
|
+
|
61
|
+
if was_new:
|
62
|
+
self._size += 1
|
63
|
+
|
64
|
+
# Keep size in sync
|
65
|
+
self._size = len(self._set)
|
66
|
+
|
67
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
68
|
+
"""Retrieve a value by key (check if element exists in set)."""
|
69
|
+
element = str(key) if not self.case_sensitive else str(key)
|
70
|
+
if not self.case_sensitive:
|
71
|
+
element = element.lower()
|
72
|
+
|
73
|
+
return self._values.get(element, default)
|
74
|
+
|
75
|
+
def has(self, key: Any) -> bool:
|
76
|
+
"""Check if key exists in set (O(1) membership test)."""
|
77
|
+
element = str(key) if not self.case_sensitive else str(key)
|
78
|
+
if not self.case_sensitive:
|
79
|
+
element = element.lower()
|
80
|
+
|
81
|
+
return element in self._set
|
82
|
+
|
83
|
+
def remove(self, key: Any) -> bool:
|
84
|
+
"""Remove element from set."""
|
85
|
+
element = str(key) if not self.case_sensitive else str(key)
|
86
|
+
if not self.case_sensitive:
|
87
|
+
element = element.lower()
|
88
|
+
|
89
|
+
if element in self._set:
|
90
|
+
self._set.remove(element)
|
91
|
+
if element in self._values:
|
92
|
+
del self._values[element]
|
93
|
+
self._size -= 1
|
94
|
+
return True
|
95
|
+
return False
|
96
|
+
|
97
|
+
def delete(self, key: Any) -> bool:
|
98
|
+
"""Remove element from set (alias for remove)."""
|
99
|
+
return self.remove(key)
|
100
|
+
|
101
|
+
def clear(self) -> None:
|
102
|
+
"""Clear all elements from the set."""
|
103
|
+
self._set.clear()
|
104
|
+
self._values.clear()
|
105
|
+
self._size = 0
|
106
|
+
|
107
|
+
def keys(self) -> Iterator[str]:
|
108
|
+
"""Get all elements in the set."""
|
109
|
+
return iter(self._set)
|
110
|
+
|
111
|
+
def values(self) -> Iterator[Any]:
|
112
|
+
"""Get all values (for compatibility)."""
|
113
|
+
return iter(self._values.values())
|
114
|
+
|
115
|
+
def items(self) -> Iterator[tuple[str, Any]]:
|
116
|
+
"""Get all element-value pairs."""
|
117
|
+
return iter(self._values.items())
|
118
|
+
|
119
|
+
def __len__(self) -> int:
|
120
|
+
"""Get the number of elements in the set."""
|
121
|
+
return self._size
|
122
|
+
|
123
|
+
def to_native(self) -> Set[Any]:
|
124
|
+
"""Convert to native Python set."""
|
125
|
+
return set(self._set)
|
126
|
+
|
127
|
+
@property
|
128
|
+
def is_list(self) -> bool:
|
129
|
+
"""This is not a list strategy."""
|
130
|
+
return False
|
131
|
+
|
132
|
+
@property
|
133
|
+
def is_dict(self) -> bool:
|
134
|
+
"""This behaves like a dict but represents a set."""
|
135
|
+
return True
|
136
|
+
|
137
|
+
# ============================================================================
|
138
|
+
# SET-SPECIFIC OPERATIONS
|
139
|
+
# ============================================================================
|
140
|
+
|
141
|
+
def add(self, element: Any) -> bool:
|
142
|
+
"""Add an element to the set. Returns True if element was new."""
|
143
|
+
element_str = str(element) if not self.case_sensitive else str(element)
|
144
|
+
if not self.case_sensitive:
|
145
|
+
element_str = element_str.lower()
|
146
|
+
|
147
|
+
was_new = element_str not in self._set
|
148
|
+
self._set.add(element_str)
|
149
|
+
self._values[element_str] = element
|
150
|
+
|
151
|
+
if was_new:
|
152
|
+
self._size += 1
|
153
|
+
|
154
|
+
return was_new
|
155
|
+
|
156
|
+
def discard(self, element: Any) -> None:
|
157
|
+
"""Remove element if present (no error if not found)."""
|
158
|
+
self.remove(element)
|
159
|
+
|
160
|
+
def pop(self) -> Any:
|
161
|
+
"""Remove and return an arbitrary element."""
|
162
|
+
if not self._set:
|
163
|
+
raise KeyError("pop from empty set")
|
164
|
+
|
165
|
+
element = self._set.pop()
|
166
|
+
value = self._values.pop(element, element)
|
167
|
+
self._size -= 1
|
168
|
+
return value
|
169
|
+
|
170
|
+
def union(self, other: Union['xSetHashStrategy', Set, List]) -> 'xSetHashStrategy':
|
171
|
+
"""Return union of this set with another."""
|
172
|
+
result = xSetHashStrategy(
|
173
|
+
traits=self._traits,
|
174
|
+
case_sensitive=self.case_sensitive,
|
175
|
+
allow_none=self.allow_none
|
176
|
+
)
|
177
|
+
|
178
|
+
# Add all elements from this set
|
179
|
+
for element in self._set:
|
180
|
+
result.add(self._values[element])
|
181
|
+
|
182
|
+
# Add elements from other
|
183
|
+
if isinstance(other, xSetHashStrategy):
|
184
|
+
for element in other._set:
|
185
|
+
result.add(other._values[element])
|
186
|
+
elif isinstance(other, (set, list)):
|
187
|
+
for element in other:
|
188
|
+
result.add(element)
|
189
|
+
|
190
|
+
return result
|
191
|
+
|
192
|
+
def intersection(self, other: Union['xSetHashStrategy', Set, List]) -> 'xSetHashStrategy':
|
193
|
+
"""Return intersection of this set with another."""
|
194
|
+
result = xSetHashStrategy(
|
195
|
+
traits=self._traits,
|
196
|
+
case_sensitive=self.case_sensitive,
|
197
|
+
allow_none=self.allow_none
|
198
|
+
)
|
199
|
+
|
200
|
+
if isinstance(other, xSetHashStrategy):
|
201
|
+
common = self._set.intersection(other._set)
|
202
|
+
for element in common:
|
203
|
+
result.add(self._values[element])
|
204
|
+
elif isinstance(other, (set, list)):
|
205
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
206
|
+
common = self._set.intersection(other_set)
|
207
|
+
for element in common:
|
208
|
+
result.add(self._values[element])
|
209
|
+
|
210
|
+
return result
|
211
|
+
|
212
|
+
def difference(self, other: Union['xSetHashStrategy', Set, List]) -> 'xSetHashStrategy':
|
213
|
+
"""Return difference of this set with another."""
|
214
|
+
result = xSetHashStrategy(
|
215
|
+
traits=self._traits,
|
216
|
+
case_sensitive=self.case_sensitive,
|
217
|
+
allow_none=self.allow_none
|
218
|
+
)
|
219
|
+
|
220
|
+
if isinstance(other, xSetHashStrategy):
|
221
|
+
diff = self._set.difference(other._set)
|
222
|
+
elif isinstance(other, (set, list)):
|
223
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
224
|
+
diff = self._set.difference(other_set)
|
225
|
+
else:
|
226
|
+
diff = self._set.copy()
|
227
|
+
|
228
|
+
for element in diff:
|
229
|
+
result.add(self._values[element])
|
230
|
+
|
231
|
+
return result
|
232
|
+
|
233
|
+
def symmetric_difference(self, other: Union['xSetHashStrategy', Set, List]) -> 'xSetHashStrategy':
|
234
|
+
"""Return symmetric difference of this set with another."""
|
235
|
+
result = xSetHashStrategy(
|
236
|
+
traits=self._traits,
|
237
|
+
case_sensitive=self.case_sensitive,
|
238
|
+
allow_none=self.allow_none
|
239
|
+
)
|
240
|
+
|
241
|
+
if isinstance(other, xSetHashStrategy):
|
242
|
+
sym_diff = self._set.symmetric_difference(other._set)
|
243
|
+
for element in sym_diff:
|
244
|
+
if element in self._values:
|
245
|
+
result.add(self._values[element])
|
246
|
+
elif element in other._values:
|
247
|
+
result.add(other._values[element])
|
248
|
+
elif isinstance(other, (set, list)):
|
249
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
250
|
+
sym_diff = self._set.symmetric_difference(other_set)
|
251
|
+
for element in sym_diff:
|
252
|
+
if element in self._values:
|
253
|
+
result.add(self._values[element])
|
254
|
+
else:
|
255
|
+
# Find original element in other
|
256
|
+
for orig in other:
|
257
|
+
if (str(orig).lower() if not self.case_sensitive else str(orig)) == element:
|
258
|
+
result.add(orig)
|
259
|
+
break
|
260
|
+
|
261
|
+
return result
|
262
|
+
|
263
|
+
def is_subset(self, other: Union['xSetHashStrategy', Set, List]) -> bool:
|
264
|
+
"""Check if this set is a subset of another."""
|
265
|
+
if isinstance(other, xSetHashStrategy):
|
266
|
+
return self._set.issubset(other._set)
|
267
|
+
elif isinstance(other, (set, list)):
|
268
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
269
|
+
return self._set.issubset(other_set)
|
270
|
+
return False
|
271
|
+
|
272
|
+
def is_superset(self, other: Union['xSetHashStrategy', Set, List]) -> bool:
|
273
|
+
"""Check if this set is a superset of another."""
|
274
|
+
if isinstance(other, xSetHashStrategy):
|
275
|
+
return self._set.issuperset(other._set)
|
276
|
+
elif isinstance(other, (set, list)):
|
277
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
278
|
+
return self._set.issuperset(other_set)
|
279
|
+
return False
|
280
|
+
|
281
|
+
def is_disjoint(self, other: Union['xSetHashStrategy', Set, List]) -> bool:
|
282
|
+
"""Check if this set has no elements in common with another."""
|
283
|
+
if isinstance(other, xSetHashStrategy):
|
284
|
+
return self._set.isdisjoint(other._set)
|
285
|
+
elif isinstance(other, (set, list)):
|
286
|
+
other_set = {str(x).lower() if not self.case_sensitive else str(x) for x in other}
|
287
|
+
return self._set.isdisjoint(other_set)
|
288
|
+
return True
|
289
|
+
|
290
|
+
def copy(self) -> 'xSetHashStrategy':
|
291
|
+
"""Create a shallow copy of the set."""
|
292
|
+
result = xSetHashStrategy(
|
293
|
+
traits=self._traits,
|
294
|
+
case_sensitive=self.case_sensitive,
|
295
|
+
allow_none=self.allow_none
|
296
|
+
)
|
297
|
+
|
298
|
+
for element in self._set:
|
299
|
+
result.add(self._values[element])
|
300
|
+
|
301
|
+
return result
|
302
|
+
|
303
|
+
def update(self, *others) -> None:
|
304
|
+
"""Update the set with elements from other iterables."""
|
305
|
+
for other in others:
|
306
|
+
if isinstance(other, xSetHashStrategy):
|
307
|
+
for element in other._set:
|
308
|
+
self.add(other._values[element])
|
309
|
+
elif isinstance(other, (set, list, tuple)):
|
310
|
+
for element in other:
|
311
|
+
self.add(element)
|
312
|
+
|
313
|
+
def get_hash(self) -> str:
|
314
|
+
"""Get a hash representation of the set."""
|
315
|
+
# Sort elements for consistent hashing
|
316
|
+
sorted_elements = sorted(self._set)
|
317
|
+
content = ''.join(sorted_elements)
|
318
|
+
return hashlib.md5(content.encode()).hexdigest()
|
319
|
+
|
320
|
+
# ============================================================================
|
321
|
+
# PERFORMANCE CHARACTERISTICS
|
322
|
+
# ============================================================================
|
323
|
+
|
324
|
+
@property
|
325
|
+
def backend_info(self) -> Dict[str, Any]:
|
326
|
+
"""Get backend implementation info."""
|
327
|
+
return {
|
328
|
+
'strategy': 'SET_HASH',
|
329
|
+
'backend': 'Python set + dict',
|
330
|
+
'case_sensitive': self.case_sensitive,
|
331
|
+
'allow_none': self.allow_none,
|
332
|
+
'complexity': {
|
333
|
+
'add': 'O(1) average',
|
334
|
+
'remove': 'O(1) average',
|
335
|
+
'contains': 'O(1) average',
|
336
|
+
'union': 'O(n + m)',
|
337
|
+
'intersection': 'O(min(n, m))',
|
338
|
+
'space': 'O(n)'
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
@property
|
343
|
+
def metrics(self) -> Dict[str, Any]:
|
344
|
+
"""Get performance metrics."""
|
345
|
+
collision_estimate = max(0, len(self._values) - len(self._set))
|
346
|
+
|
347
|
+
return {
|
348
|
+
'size': self._size,
|
349
|
+
'unique_elements': len(self._set),
|
350
|
+
'collisions_estimate': collision_estimate,
|
351
|
+
'load_factor': self.load_factor,
|
352
|
+
'memory_usage': f"{self._size * 32} bytes (estimated)",
|
353
|
+
'hash': self.get_hash()[:8] + "..."
|
354
|
+
}
|