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,307 @@
|
|
1
|
+
"""
|
2
|
+
Abstract Node Strategy Interface
|
3
|
+
|
4
|
+
This module defines the abstract base class that all node strategies must implement
|
5
|
+
in the strategy system.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from typing import Any, Dict, List, Optional, Iterator, Union
|
10
|
+
from ...types import NodeMode, NodeTrait
|
11
|
+
from ...errors import XWNodeUnsupportedCapabilityError
|
12
|
+
|
13
|
+
|
14
|
+
class aNodeStrategy(ABC):
|
15
|
+
"""
|
16
|
+
Abstract base class for all node strategies.
|
17
|
+
|
18
|
+
This abstract base class defines the contract that all node strategy
|
19
|
+
implementations must follow, ensuring consistency and interoperability.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, mode: NodeMode, traits: NodeTrait = NodeTrait.NONE, **options):
|
23
|
+
"""Initialize the abstract node strategy."""
|
24
|
+
self.mode = mode
|
25
|
+
self.traits = traits
|
26
|
+
self.options = options
|
27
|
+
self._data: Dict[str, Any] = {}
|
28
|
+
self._size = 0
|
29
|
+
|
30
|
+
# Validate traits compatibility with mode
|
31
|
+
self._validate_traits()
|
32
|
+
|
33
|
+
def _validate_traits(self) -> None:
|
34
|
+
"""Validate that the requested traits are compatible with this strategy."""
|
35
|
+
supported_traits = self.get_supported_traits()
|
36
|
+
unsupported = self.traits & ~supported_traits
|
37
|
+
if unsupported != NodeTrait.NONE:
|
38
|
+
unsupported_names = [trait.name for trait in NodeTrait if trait in unsupported]
|
39
|
+
raise ValueError(f"Strategy {self.mode.name} does not support traits: {unsupported_names}")
|
40
|
+
|
41
|
+
def get_supported_traits(self) -> NodeTrait:
|
42
|
+
"""Get the traits supported by this strategy implementation."""
|
43
|
+
# Default implementation - subclasses should override
|
44
|
+
return NodeTrait.NONE
|
45
|
+
|
46
|
+
def has_trait(self, trait: NodeTrait) -> bool:
|
47
|
+
"""Check if this strategy has a specific trait."""
|
48
|
+
return bool(self.traits & trait)
|
49
|
+
|
50
|
+
def require_trait(self, trait: NodeTrait, operation: str = "operation") -> None:
|
51
|
+
"""Require a specific trait for an operation."""
|
52
|
+
if not self.has_trait(trait):
|
53
|
+
from ...errors import UnsupportedCapabilityError
|
54
|
+
raise UnsupportedCapabilityError(f"{operation} requires {trait.name} capability")
|
55
|
+
|
56
|
+
# ============================================================================
|
57
|
+
# CORE OPERATIONS (Required)
|
58
|
+
# ============================================================================
|
59
|
+
|
60
|
+
@abstractmethod
|
61
|
+
def put(self, key: Any, value: Any = None) -> None:
|
62
|
+
"""
|
63
|
+
Store a key-value pair.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
key: The key to store
|
67
|
+
value: The value to associate with the key
|
68
|
+
"""
|
69
|
+
pass
|
70
|
+
|
71
|
+
@abstractmethod
|
72
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
73
|
+
"""
|
74
|
+
Retrieve a value by key.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
key: The key to look up
|
78
|
+
default: Default value if key not found
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
The value associated with the key, or default if not found
|
82
|
+
"""
|
83
|
+
pass
|
84
|
+
|
85
|
+
@abstractmethod
|
86
|
+
def delete(self, key: Any) -> bool:
|
87
|
+
"""
|
88
|
+
Remove a key-value pair.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
key: The key to remove
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
True if key was found and removed, False otherwise
|
95
|
+
"""
|
96
|
+
pass
|
97
|
+
|
98
|
+
@abstractmethod
|
99
|
+
def has(self, key: Any) -> bool:
|
100
|
+
"""
|
101
|
+
Check if a key exists.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
key: The key to check
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
True if key exists, False otherwise
|
108
|
+
"""
|
109
|
+
pass
|
110
|
+
|
111
|
+
@abstractmethod
|
112
|
+
def __len__(self) -> int:
|
113
|
+
"""Get the number of key-value pairs."""
|
114
|
+
pass
|
115
|
+
|
116
|
+
@abstractmethod
|
117
|
+
def keys(self) -> Iterator[Any]:
|
118
|
+
"""Get an iterator over all keys."""
|
119
|
+
pass
|
120
|
+
|
121
|
+
@abstractmethod
|
122
|
+
def values(self) -> Iterator[Any]:
|
123
|
+
"""Get an iterator over all values."""
|
124
|
+
pass
|
125
|
+
|
126
|
+
@abstractmethod
|
127
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
128
|
+
"""Get an iterator over all key-value pairs."""
|
129
|
+
pass
|
130
|
+
|
131
|
+
# ============================================================================
|
132
|
+
# CAPABILITY-BASED OPERATIONS (Optional)
|
133
|
+
# ============================================================================
|
134
|
+
|
135
|
+
def get_ordered(self, start: Any = None, end: Any = None) -> List[tuple[Any, Any]]:
|
136
|
+
"""
|
137
|
+
Get items in order (requires ORDERED trait).
|
138
|
+
|
139
|
+
Args:
|
140
|
+
start: Start key (inclusive)
|
141
|
+
end: End key (exclusive)
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
List of (key, value) pairs in order
|
145
|
+
|
146
|
+
Raises:
|
147
|
+
UnsupportedCapabilityError: If ORDERED trait not supported
|
148
|
+
"""
|
149
|
+
if NodeTrait.ORDERED not in self.traits:
|
150
|
+
raise XWNodeUnsupportedCapabilityError("ORDERED", self.mode.name, [str(t) for t in self.traits])
|
151
|
+
|
152
|
+
# Default implementation for ordered strategies
|
153
|
+
items = list(self.items())
|
154
|
+
if start is not None:
|
155
|
+
items = [(k, v) for k, v in items if k >= start]
|
156
|
+
if end is not None:
|
157
|
+
items = [(k, v) for k, v in items if k < end]
|
158
|
+
return items
|
159
|
+
|
160
|
+
def get_with_prefix(self, prefix: str) -> List[tuple[Any, Any]]:
|
161
|
+
"""
|
162
|
+
Get items with given prefix (requires HIERARCHICAL trait).
|
163
|
+
|
164
|
+
Args:
|
165
|
+
prefix: The prefix to match
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
List of (key, value) pairs with matching prefix
|
169
|
+
|
170
|
+
Raises:
|
171
|
+
UnsupportedCapabilityError: If HIERARCHICAL trait not supported
|
172
|
+
"""
|
173
|
+
if NodeTrait.HIERARCHICAL not in self.traits:
|
174
|
+
raise XWNodeUnsupportedCapabilityError("HIERARCHICAL", self.mode.name, [str(t) for t in self.traits])
|
175
|
+
|
176
|
+
# Default implementation for hierarchical strategies
|
177
|
+
return [(k, v) for k, v in self.items() if str(k).startswith(prefix)]
|
178
|
+
|
179
|
+
def get_priority(self) -> Optional[tuple[Any, Any]]:
|
180
|
+
"""
|
181
|
+
Get highest priority item (requires PRIORITY trait).
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
(key, value) pair with highest priority, or None if empty
|
185
|
+
|
186
|
+
Raises:
|
187
|
+
UnsupportedCapabilityError: If PRIORITY trait not supported
|
188
|
+
"""
|
189
|
+
if NodeTrait.PRIORITY not in self.traits:
|
190
|
+
raise XWNodeUnsupportedCapabilityError("PRIORITY", self.mode.name, [str(t) for t in self.traits])
|
191
|
+
|
192
|
+
# Default implementation for priority strategies
|
193
|
+
if not self._data:
|
194
|
+
return None
|
195
|
+
return min(self.items(), key=lambda x: x[0])
|
196
|
+
|
197
|
+
def get_weighted(self, key: Any) -> float:
|
198
|
+
"""
|
199
|
+
Get weight for a key (requires WEIGHTED trait).
|
200
|
+
|
201
|
+
Args:
|
202
|
+
key: The key to get weight for
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Weight value for the key
|
206
|
+
|
207
|
+
Raises:
|
208
|
+
UnsupportedCapabilityError: If WEIGHTED trait not supported
|
209
|
+
"""
|
210
|
+
if NodeTrait.WEIGHTED not in self.traits:
|
211
|
+
raise XWNodeUnsupportedCapabilityError("WEIGHTED", self.mode.name, [str(t) for t in self.traits])
|
212
|
+
|
213
|
+
# Default implementation for weighted strategies
|
214
|
+
return self._data.get(key, {}).get('weight', 1.0)
|
215
|
+
|
216
|
+
# ============================================================================
|
217
|
+
# STRATEGY METADATA
|
218
|
+
# ============================================================================
|
219
|
+
|
220
|
+
def capabilities(self) -> NodeTrait:
|
221
|
+
"""Get the capabilities supported by this strategy."""
|
222
|
+
return self.traits
|
223
|
+
|
224
|
+
def backend_info(self) -> Dict[str, Any]:
|
225
|
+
"""Get information about the backend implementation."""
|
226
|
+
return {
|
227
|
+
"mode": self.mode.name,
|
228
|
+
"traits": str(self.traits),
|
229
|
+
"size": len(self),
|
230
|
+
"options": self.options.copy()
|
231
|
+
}
|
232
|
+
|
233
|
+
def metrics(self) -> Dict[str, Any]:
|
234
|
+
"""Get performance metrics for this strategy."""
|
235
|
+
return {
|
236
|
+
"size": len(self),
|
237
|
+
"mode": self.mode.name,
|
238
|
+
"traits": str(self.traits)
|
239
|
+
}
|
240
|
+
|
241
|
+
# ============================================================================
|
242
|
+
# FACTORY METHODS
|
243
|
+
# ============================================================================
|
244
|
+
|
245
|
+
@classmethod
|
246
|
+
def create_from_data(cls, data: Any) -> 'aNodeStrategy':
|
247
|
+
"""
|
248
|
+
Create a new strategy instance from data.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
data: The data to create the strategy from
|
252
|
+
|
253
|
+
Returns:
|
254
|
+
A new strategy instance containing the data
|
255
|
+
"""
|
256
|
+
instance = cls()
|
257
|
+
if isinstance(data, dict):
|
258
|
+
for key, value in data.items():
|
259
|
+
instance.put(key, value)
|
260
|
+
elif isinstance(data, (list, tuple)):
|
261
|
+
for i, value in enumerate(data):
|
262
|
+
instance.put(i, value)
|
263
|
+
else:
|
264
|
+
# For primitive values, store as root value
|
265
|
+
instance.put('_value', data)
|
266
|
+
return instance
|
267
|
+
|
268
|
+
# ============================================================================
|
269
|
+
# UTILITY METHODS
|
270
|
+
# ============================================================================
|
271
|
+
|
272
|
+
def clear(self) -> None:
|
273
|
+
"""Clear all data."""
|
274
|
+
self._data.clear()
|
275
|
+
self._size = 0
|
276
|
+
|
277
|
+
def __contains__(self, key: Any) -> bool:
|
278
|
+
"""Check if key exists."""
|
279
|
+
return self.has(key)
|
280
|
+
|
281
|
+
def __getitem__(self, key: Any) -> Any:
|
282
|
+
"""Get value by key."""
|
283
|
+
return self.get(key)
|
284
|
+
|
285
|
+
def __setitem__(self, key: Any, value: Any) -> None:
|
286
|
+
"""Set value by key."""
|
287
|
+
self.put(key, value)
|
288
|
+
|
289
|
+
def __delitem__(self, key: Any) -> None:
|
290
|
+
"""Delete key."""
|
291
|
+
if not self.delete(key):
|
292
|
+
raise KeyError(key)
|
293
|
+
|
294
|
+
def __iter__(self) -> Iterator[Any]:
|
295
|
+
"""Iterate over keys."""
|
296
|
+
return self.keys()
|
297
|
+
|
298
|
+
def __str__(self) -> str:
|
299
|
+
"""String representation."""
|
300
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, size={len(self)})"
|
301
|
+
|
302
|
+
def __repr__(self) -> str:
|
303
|
+
"""Detailed string representation."""
|
304
|
+
return f"{self.__class__.__name__}(mode={self.mode.name}, traits={self.traits}, size={len(self)})"
|
305
|
+
|
306
|
+
|
307
|
+
|
@@ -0,0 +1,267 @@
|
|
1
|
+
"""
|
2
|
+
Adjacency List Strategy Implementation
|
3
|
+
|
4
|
+
Implements graph operations using adjacency list representation.
|
5
|
+
|
6
|
+
Company: eXonware.com
|
7
|
+
Author: Eng. Muhammad AlShehri
|
8
|
+
Email: connect@exonware.com
|
9
|
+
Version: 0.0.1.12
|
10
|
+
Generation Date: 07-Sep-2025
|
11
|
+
"""
|
12
|
+
|
13
|
+
from typing import Any, Iterator, List, Optional, Dict, Union, Set, Tuple
|
14
|
+
from collections import defaultdict
|
15
|
+
from .base import ANodeGraphStrategy
|
16
|
+
from ...types import NodeMode, NodeTrait
|
17
|
+
|
18
|
+
|
19
|
+
class AdjacencyListStrategy(ANodeGraphStrategy):
|
20
|
+
"""
|
21
|
+
Adjacency List node strategy for graph operations.
|
22
|
+
|
23
|
+
Uses adjacency list representation for efficient neighbor queries
|
24
|
+
and edge operations in sparse graphs.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self):
|
28
|
+
"""Initialize an empty adjacency list."""
|
29
|
+
super().__init__()
|
30
|
+
self._adj_list: Dict[str, List[Tuple[str, float]]] = defaultdict(list) # node -> [(neighbor, weight)]
|
31
|
+
self._nodes: Dict[str, Any] = {} # node -> data
|
32
|
+
self._mode = NodeMode.ADJACENCY_LIST
|
33
|
+
self._traits = {NodeTrait.GRAPH, NodeTrait.SPARSE, NodeTrait.FAST_NEIGHBORS}
|
34
|
+
|
35
|
+
def insert(self, key: str, value: Any) -> None:
|
36
|
+
"""Insert a node into the graph."""
|
37
|
+
self._nodes[key] = value
|
38
|
+
if key not in self._adj_list:
|
39
|
+
self._adj_list[key] = []
|
40
|
+
|
41
|
+
def find(self, key: str) -> Optional[Any]:
|
42
|
+
"""Find a node in the graph."""
|
43
|
+
return self._nodes.get(key)
|
44
|
+
|
45
|
+
def delete(self, key: str) -> bool:
|
46
|
+
"""Delete a node and all its edges."""
|
47
|
+
if key not in self._nodes:
|
48
|
+
return False
|
49
|
+
|
50
|
+
# Remove node
|
51
|
+
del self._nodes[key]
|
52
|
+
|
53
|
+
# Remove all edges to this node
|
54
|
+
for node in self._adj_list:
|
55
|
+
self._adj_list[node] = [(neighbor, weight) for neighbor, weight in self._adj_list[node] if neighbor != key]
|
56
|
+
|
57
|
+
# Remove node's adjacency list
|
58
|
+
if key in self._adj_list:
|
59
|
+
del self._adj_list[key]
|
60
|
+
|
61
|
+
return True
|
62
|
+
|
63
|
+
def size(self) -> int:
|
64
|
+
"""Get the number of nodes in the graph."""
|
65
|
+
return len(self._nodes)
|
66
|
+
|
67
|
+
def to_native(self) -> Dict[str, Any]:
|
68
|
+
"""Convert graph to native dictionary format."""
|
69
|
+
return {
|
70
|
+
'nodes': self._nodes,
|
71
|
+
'edges': {node: neighbors for node, neighbors in self._adj_list.items() if neighbors}
|
72
|
+
}
|
73
|
+
|
74
|
+
def from_native(self, data: Dict[str, Any]) -> None:
|
75
|
+
"""Load graph from native dictionary format."""
|
76
|
+
self._nodes = data.get('nodes', {})
|
77
|
+
edges = data.get('edges', {})
|
78
|
+
|
79
|
+
self._adj_list.clear()
|
80
|
+
for node, neighbors in edges.items():
|
81
|
+
self._adj_list[node] = neighbors
|
82
|
+
|
83
|
+
def add_edge(self, from_node: str, to_node: str, weight: float = 1.0) -> None:
|
84
|
+
"""Add an edge between two nodes."""
|
85
|
+
if from_node not in self._nodes:
|
86
|
+
self._nodes[from_node] = None
|
87
|
+
if to_node not in self._nodes:
|
88
|
+
self._nodes[to_node] = None
|
89
|
+
|
90
|
+
# Add edge (avoid duplicates)
|
91
|
+
neighbors = self._adj_list[from_node]
|
92
|
+
for i, (neighbor, _) in enumerate(neighbors):
|
93
|
+
if neighbor == to_node:
|
94
|
+
neighbors[i] = (to_node, weight)
|
95
|
+
return
|
96
|
+
|
97
|
+
neighbors.append((to_node, weight))
|
98
|
+
|
99
|
+
def remove_edge(self, from_node: str, to_node: str) -> bool:
|
100
|
+
"""Remove an edge between two nodes."""
|
101
|
+
if from_node not in self._adj_list:
|
102
|
+
return False
|
103
|
+
|
104
|
+
neighbors = self._adj_list[from_node]
|
105
|
+
for i, (neighbor, _) in enumerate(neighbors):
|
106
|
+
if neighbor == to_node:
|
107
|
+
neighbors.pop(i)
|
108
|
+
return True
|
109
|
+
return False
|
110
|
+
|
111
|
+
def has_edge(self, from_node: str, to_node: str) -> bool:
|
112
|
+
"""Check if an edge exists between two nodes."""
|
113
|
+
if from_node not in self._adj_list:
|
114
|
+
return False
|
115
|
+
|
116
|
+
for neighbor, _ in self._adj_list[from_node]:
|
117
|
+
if neighbor == to_node:
|
118
|
+
return True
|
119
|
+
return False
|
120
|
+
|
121
|
+
def get_edge_weight(self, from_node: str, to_node: str) -> Optional[float]:
|
122
|
+
"""Get the weight of an edge between two nodes."""
|
123
|
+
if from_node not in self._adj_list:
|
124
|
+
return None
|
125
|
+
|
126
|
+
for neighbor, weight in self._adj_list[from_node]:
|
127
|
+
if neighbor == to_node:
|
128
|
+
return weight
|
129
|
+
return None
|
130
|
+
|
131
|
+
def get_neighbors(self, node: str) -> List[str]:
|
132
|
+
"""Get all neighbors of a node."""
|
133
|
+
if node not in self._adj_list:
|
134
|
+
return []
|
135
|
+
return [neighbor for neighbor, _ in self._adj_list[node]]
|
136
|
+
|
137
|
+
def get_neighbors_with_weights(self, node: str) -> List[Tuple[str, float]]:
|
138
|
+
"""Get all neighbors with their edge weights."""
|
139
|
+
if node not in self._adj_list:
|
140
|
+
return []
|
141
|
+
return self._adj_list[node].copy()
|
142
|
+
|
143
|
+
def get_in_degree(self, node: str) -> int:
|
144
|
+
"""Get the in-degree of a node."""
|
145
|
+
count = 0
|
146
|
+
for neighbors in self._adj_list.values():
|
147
|
+
for neighbor, _ in neighbors:
|
148
|
+
if neighbor == node:
|
149
|
+
count += 1
|
150
|
+
return count
|
151
|
+
|
152
|
+
def get_out_degree(self, node: str) -> int:
|
153
|
+
"""Get the out-degree of a node."""
|
154
|
+
if node not in self._adj_list:
|
155
|
+
return 0
|
156
|
+
return len(self._adj_list[node])
|
157
|
+
|
158
|
+
def get_degree(self, node: str) -> int:
|
159
|
+
"""Get the total degree of a node (in + out)."""
|
160
|
+
return self.get_in_degree(node) + self.get_out_degree(node)
|
161
|
+
|
162
|
+
def is_connected(self, from_node: str, to_node: str) -> bool:
|
163
|
+
"""Check if two nodes are connected (BFS)."""
|
164
|
+
if from_node not in self._nodes or to_node not in self._nodes:
|
165
|
+
return False
|
166
|
+
|
167
|
+
if from_node == to_node:
|
168
|
+
return True
|
169
|
+
|
170
|
+
visited = set()
|
171
|
+
queue = [from_node]
|
172
|
+
|
173
|
+
while queue:
|
174
|
+
current = queue.pop(0)
|
175
|
+
if current == to_node:
|
176
|
+
return True
|
177
|
+
|
178
|
+
if current in visited:
|
179
|
+
continue
|
180
|
+
visited.add(current)
|
181
|
+
|
182
|
+
for neighbor, _ in self._adj_list.get(current, []):
|
183
|
+
if neighbor not in visited:
|
184
|
+
queue.append(neighbor)
|
185
|
+
|
186
|
+
return False
|
187
|
+
|
188
|
+
def get_connected_components(self) -> List[Set[str]]:
|
189
|
+
"""Get all connected components in the graph."""
|
190
|
+
visited = set()
|
191
|
+
components = []
|
192
|
+
|
193
|
+
for node in self._nodes:
|
194
|
+
if node not in visited:
|
195
|
+
component = set()
|
196
|
+
queue = [node]
|
197
|
+
|
198
|
+
while queue:
|
199
|
+
current = queue.pop(0)
|
200
|
+
if current in visited:
|
201
|
+
continue
|
202
|
+
|
203
|
+
visited.add(current)
|
204
|
+
component.add(current)
|
205
|
+
|
206
|
+
# Add neighbors
|
207
|
+
for neighbor, _ in self._adj_list.get(current, []):
|
208
|
+
if neighbor not in visited:
|
209
|
+
queue.append(neighbor)
|
210
|
+
|
211
|
+
if component:
|
212
|
+
components.append(component)
|
213
|
+
|
214
|
+
return components
|
215
|
+
|
216
|
+
def clear(self) -> None:
|
217
|
+
"""Clear all nodes and edges."""
|
218
|
+
self._nodes.clear()
|
219
|
+
self._adj_list.clear()
|
220
|
+
|
221
|
+
def __iter__(self) -> Iterator[str]:
|
222
|
+
"""Iterate through all nodes."""
|
223
|
+
for node in self._nodes:
|
224
|
+
yield node
|
225
|
+
|
226
|
+
def __repr__(self) -> str:
|
227
|
+
"""String representation of the adjacency list."""
|
228
|
+
return f"AdjacencyListStrategy({len(self._nodes)} nodes, {sum(len(neighbors) for neighbors in self._adj_list.values())} edges)"
|
229
|
+
|
230
|
+
# Required abstract methods from base classes
|
231
|
+
def find_path(self, start: Any, end: Any) -> List[Any]:
|
232
|
+
"""Find path between nodes using BFS."""
|
233
|
+
if start not in self._nodes or end not in self._nodes:
|
234
|
+
return []
|
235
|
+
|
236
|
+
if start == end:
|
237
|
+
return [start]
|
238
|
+
|
239
|
+
visited = set()
|
240
|
+
queue = [(start, [start])]
|
241
|
+
|
242
|
+
while queue:
|
243
|
+
current, path = queue.pop(0)
|
244
|
+
if current == end:
|
245
|
+
return path
|
246
|
+
|
247
|
+
if current in visited:
|
248
|
+
continue
|
249
|
+
visited.add(current)
|
250
|
+
|
251
|
+
for neighbor, _ in self._adj_list.get(current, []):
|
252
|
+
if neighbor not in visited:
|
253
|
+
queue.append((neighbor, path + [neighbor]))
|
254
|
+
|
255
|
+
return [] # No path found
|
256
|
+
|
257
|
+
def as_union_find(self):
|
258
|
+
"""Provide Union-Find behavioral view."""
|
259
|
+
return self
|
260
|
+
|
261
|
+
def as_neural_graph(self):
|
262
|
+
"""Provide Neural Graph behavioral view."""
|
263
|
+
return self
|
264
|
+
|
265
|
+
def as_flow_network(self):
|
266
|
+
"""Provide Flow Network behavioral view."""
|
267
|
+
return self
|