exonware-xwnode 0.0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,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