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,227 @@
|
|
1
|
+
"""
|
2
|
+
Adjacency List Edge Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the ADJ_LIST strategy for sparse graph representation
|
5
|
+
with efficient edge addition and neighbor queries.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple
|
9
|
+
from collections import defaultdict
|
10
|
+
from .base import AGraphEdgeStrategy
|
11
|
+
from ...types import EdgeMode, EdgeTrait
|
12
|
+
|
13
|
+
|
14
|
+
class AdjListStrategy(AGraphEdgeStrategy):
|
15
|
+
"""
|
16
|
+
Adjacency List edge strategy for sparse graph representation.
|
17
|
+
|
18
|
+
Provides O(1) edge addition and O(degree) neighbor queries,
|
19
|
+
ideal for sparse graphs where most vertices have few connections.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
23
|
+
"""Initialize the Adjacency List strategy."""
|
24
|
+
super().__init__(**options)
|
25
|
+
self._mode = EdgeMode.ADJ_LIST
|
26
|
+
self._traits = traits
|
27
|
+
|
28
|
+
self.is_directed = options.get('directed', True)
|
29
|
+
self.allow_self_loops = options.get('self_loops', True)
|
30
|
+
self.allow_multi_edges = options.get('multi_edges', False)
|
31
|
+
|
32
|
+
# Core storage: vertex -> list of (neighbor, edge_data)
|
33
|
+
self._outgoing: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list)
|
34
|
+
self._incoming: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list) if self.is_directed else None
|
35
|
+
|
36
|
+
# Vertex set for fast membership testing
|
37
|
+
self._vertices: Set[str] = set()
|
38
|
+
|
39
|
+
# Edge properties storage
|
40
|
+
self._edge_count = 0
|
41
|
+
self._edge_id_counter = 0
|
42
|
+
|
43
|
+
def get_supported_traits(self) -> EdgeTrait:
|
44
|
+
"""Get the traits supported by the adjacency list strategy."""
|
45
|
+
return (EdgeTrait.SPARSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.MULTI)
|
46
|
+
|
47
|
+
# ============================================================================
|
48
|
+
# CORE EDGE OPERATIONS
|
49
|
+
# ============================================================================
|
50
|
+
|
51
|
+
def add_edge(self, from_node: Any, to_node: Any, **kwargs) -> None:
|
52
|
+
"""Add an edge between source and target vertices."""
|
53
|
+
source = str(from_node)
|
54
|
+
target = str(to_node)
|
55
|
+
|
56
|
+
# Validation
|
57
|
+
if not self.allow_self_loops and source == target:
|
58
|
+
raise ValueError("Self loops not allowed")
|
59
|
+
|
60
|
+
if not self.allow_multi_edges and self.has_edge(source, target):
|
61
|
+
raise ValueError("Multi edges not allowed")
|
62
|
+
|
63
|
+
# Add vertices to set
|
64
|
+
self._vertices.add(source)
|
65
|
+
self._vertices.add(target)
|
66
|
+
|
67
|
+
# Create edge data
|
68
|
+
edge_data = {
|
69
|
+
'weight': kwargs.get('weight', 1.0),
|
70
|
+
'id': f"edge_{self._edge_id_counter}",
|
71
|
+
**kwargs
|
72
|
+
}
|
73
|
+
self._edge_id_counter += 1
|
74
|
+
|
75
|
+
# Add outgoing edge
|
76
|
+
self._outgoing[source].append((target, edge_data))
|
77
|
+
|
78
|
+
# Add incoming edge if directed
|
79
|
+
if self.is_directed and self._incoming is not None:
|
80
|
+
self._incoming[target].append((source, edge_data))
|
81
|
+
elif not self.is_directed:
|
82
|
+
# For undirected, add reverse edge
|
83
|
+
self._outgoing[target].append((source, edge_data))
|
84
|
+
|
85
|
+
self._edge_count += 1
|
86
|
+
|
87
|
+
def remove_edge(self, from_node: Any, to_node: Any) -> bool:
|
88
|
+
"""Remove edge between vertices."""
|
89
|
+
source = str(from_node)
|
90
|
+
target = str(to_node)
|
91
|
+
|
92
|
+
removed = False
|
93
|
+
|
94
|
+
# Remove from outgoing
|
95
|
+
for i, (neighbor, _) in enumerate(self._outgoing[source]):
|
96
|
+
if neighbor == target:
|
97
|
+
self._outgoing[source].pop(i)
|
98
|
+
removed = True
|
99
|
+
break
|
100
|
+
|
101
|
+
# Remove from incoming if directed
|
102
|
+
if self.is_directed and self._incoming is not None:
|
103
|
+
for i, (neighbor, _) in enumerate(self._incoming[target]):
|
104
|
+
if neighbor == source:
|
105
|
+
self._incoming[target].pop(i)
|
106
|
+
break
|
107
|
+
elif not self.is_directed:
|
108
|
+
# For undirected, remove reverse edge
|
109
|
+
for i, (neighbor, _) in enumerate(self._outgoing[target]):
|
110
|
+
if neighbor == source:
|
111
|
+
self._outgoing[target].pop(i)
|
112
|
+
break
|
113
|
+
|
114
|
+
if removed:
|
115
|
+
self._edge_count -= 1
|
116
|
+
|
117
|
+
return removed
|
118
|
+
|
119
|
+
def has_edge(self, from_node: Any, to_node: Any) -> bool:
|
120
|
+
"""Check if edge exists."""
|
121
|
+
source = str(from_node)
|
122
|
+
target = str(to_node)
|
123
|
+
|
124
|
+
for neighbor, _ in self._outgoing[source]:
|
125
|
+
if neighbor == target:
|
126
|
+
return True
|
127
|
+
return False
|
128
|
+
|
129
|
+
def get_edge_count(self) -> int:
|
130
|
+
"""Get total number of edges."""
|
131
|
+
return self._edge_count
|
132
|
+
|
133
|
+
def get_vertex_count(self) -> int:
|
134
|
+
"""Get total number of vertices."""
|
135
|
+
return len(self._vertices)
|
136
|
+
|
137
|
+
# ============================================================================
|
138
|
+
# GRAPH EDGE STRATEGY METHODS
|
139
|
+
# ============================================================================
|
140
|
+
|
141
|
+
def get_neighbors(self, node: Any) -> List[Any]:
|
142
|
+
"""Get all neighboring nodes."""
|
143
|
+
vertex = str(node)
|
144
|
+
neighbors = []
|
145
|
+
for neighbor, _ in self._outgoing[vertex]:
|
146
|
+
neighbors.append(neighbor)
|
147
|
+
return neighbors
|
148
|
+
|
149
|
+
def get_edge_weight(self, from_node: Any, to_node: Any) -> float:
|
150
|
+
"""Get edge weight."""
|
151
|
+
source = str(from_node)
|
152
|
+
target = str(to_node)
|
153
|
+
|
154
|
+
for neighbor, edge_data in self._outgoing[source]:
|
155
|
+
if neighbor == target:
|
156
|
+
return edge_data.get('weight', 1.0)
|
157
|
+
return 0.0
|
158
|
+
|
159
|
+
def set_edge_weight(self, from_node: Any, to_node: Any, weight: float) -> None:
|
160
|
+
"""Set edge weight."""
|
161
|
+
source = str(from_node)
|
162
|
+
target = str(to_node)
|
163
|
+
|
164
|
+
for neighbor, edge_data in self._outgoing[source]:
|
165
|
+
if neighbor == target:
|
166
|
+
edge_data['weight'] = weight
|
167
|
+
return
|
168
|
+
raise ValueError(f"Edge {source} -> {target} not found")
|
169
|
+
|
170
|
+
def find_shortest_path(self, start: Any, end: Any) -> List[Any]:
|
171
|
+
"""Find shortest path between nodes."""
|
172
|
+
# TODO: Implement BFS/Dijkstra
|
173
|
+
return []
|
174
|
+
|
175
|
+
def find_all_paths(self, start: Any, end: Any) -> List[List[Any]]:
|
176
|
+
"""Find all paths between nodes."""
|
177
|
+
# TODO: Implement DFS
|
178
|
+
return []
|
179
|
+
|
180
|
+
def get_connected_components(self) -> List[List[Any]]:
|
181
|
+
"""Get all connected components."""
|
182
|
+
# TODO: Implement connected components
|
183
|
+
return []
|
184
|
+
|
185
|
+
def is_connected(self, start: Any, end: Any) -> bool:
|
186
|
+
"""Check if two nodes are connected."""
|
187
|
+
# TODO: Implement connectivity check
|
188
|
+
return False
|
189
|
+
|
190
|
+
def get_degree(self, node: Any) -> int:
|
191
|
+
"""Get degree of node."""
|
192
|
+
vertex = str(node)
|
193
|
+
return len(self._outgoing[vertex])
|
194
|
+
|
195
|
+
def is_cyclic(self) -> bool:
|
196
|
+
"""Check if graph contains cycles."""
|
197
|
+
# TODO: Implement cycle detection
|
198
|
+
return False
|
199
|
+
|
200
|
+
# ============================================================================
|
201
|
+
# PERFORMANCE CHARACTERISTICS
|
202
|
+
# ============================================================================
|
203
|
+
|
204
|
+
@property
|
205
|
+
def backend_info(self) -> Dict[str, Any]:
|
206
|
+
"""Get backend implementation info."""
|
207
|
+
return {
|
208
|
+
'strategy': 'adjacency_list',
|
209
|
+
'backend': 'Dictionary of lists',
|
210
|
+
'complexity': {
|
211
|
+
'add_edge': 'O(1)',
|
212
|
+
'remove_edge': 'O(degree)',
|
213
|
+
'has_edge': 'O(degree)',
|
214
|
+
'get_neighbors': 'O(degree)',
|
215
|
+
'space': 'O(V + E)'
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
@property
|
220
|
+
def metrics(self) -> Dict[str, Any]:
|
221
|
+
"""Get performance metrics."""
|
222
|
+
return {
|
223
|
+
'vertices': len(self._vertices),
|
224
|
+
'edges': self._edge_count,
|
225
|
+
'directed': self.is_directed,
|
226
|
+
'density': f"{(self._edge_count / max(1, len(self._vertices) * (len(self._vertices) - 1))) * 100:.1f}%"
|
227
|
+
}
|
@@ -0,0 +1,391 @@
|
|
1
|
+
"""
|
2
|
+
Adjacency Matrix Edge Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the ADJ_MATRIX strategy for dense graph representation
|
5
|
+
with O(1) edge operations and efficient matrix-based algorithms.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, Union
|
9
|
+
from .base import AGraphEdgeStrategy
|
10
|
+
from ...types import EdgeMode, EdgeTrait
|
11
|
+
|
12
|
+
|
13
|
+
class AdjMatrixStrategy(AGraphEdgeStrategy):
|
14
|
+
"""
|
15
|
+
Adjacency Matrix edge strategy for dense graph representation.
|
16
|
+
|
17
|
+
Provides O(1) edge operations and efficient matrix-based graph algorithms,
|
18
|
+
ideal for dense graphs where most vertices are connected.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
22
|
+
"""Initialize the Adjacency Matrix strategy."""
|
23
|
+
super().__init__(**options)
|
24
|
+
self._mode = EdgeMode.ADJ_MATRIX
|
25
|
+
self._traits = traits
|
26
|
+
|
27
|
+
self.is_directed = options.get('directed', True)
|
28
|
+
self.initial_capacity = options.get('initial_capacity', 100)
|
29
|
+
self.allow_self_loops = options.get('self_loops', True)
|
30
|
+
self.default_weight = options.get('default_weight', 1.0)
|
31
|
+
|
32
|
+
# Core storage: 2D matrix of edge weights/properties
|
33
|
+
self._matrix: List[List[Optional[Dict[str, Any]]]] = []
|
34
|
+
self._capacity = 0
|
35
|
+
|
36
|
+
# Vertex management
|
37
|
+
self._vertex_to_index: Dict[str, int] = {}
|
38
|
+
self._index_to_vertex: Dict[int, str] = {}
|
39
|
+
self._vertex_count = 0
|
40
|
+
self._edge_count = 0
|
41
|
+
self._edge_id_counter = 0
|
42
|
+
|
43
|
+
# Initialize matrix
|
44
|
+
self._resize_matrix(self.initial_capacity)
|
45
|
+
|
46
|
+
def get_supported_traits(self) -> EdgeTrait:
|
47
|
+
"""Get the traits supported by the adjacency matrix strategy."""
|
48
|
+
return (EdgeTrait.DENSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.CACHE_FRIENDLY)
|
49
|
+
|
50
|
+
# ============================================================================
|
51
|
+
# MATRIX MANAGEMENT
|
52
|
+
# ============================================================================
|
53
|
+
|
54
|
+
def _resize_matrix(self, new_capacity: int) -> None:
|
55
|
+
"""Resize the adjacency matrix to new capacity."""
|
56
|
+
old_capacity = self._capacity
|
57
|
+
self._capacity = new_capacity
|
58
|
+
|
59
|
+
# Create new matrix
|
60
|
+
new_matrix = [[None for _ in range(self._capacity)] for _ in range(self._capacity)]
|
61
|
+
|
62
|
+
# Copy existing data
|
63
|
+
for i in range(min(old_capacity, self._capacity)):
|
64
|
+
for j in range(min(old_capacity, self._capacity)):
|
65
|
+
new_matrix[i][j] = self._matrix[i][j]
|
66
|
+
|
67
|
+
self._matrix = new_matrix
|
68
|
+
|
69
|
+
def _get_vertex_index(self, vertex: str) -> int:
|
70
|
+
"""Get or create index for vertex."""
|
71
|
+
if vertex not in self._vertex_to_index:
|
72
|
+
if self._vertex_count >= self._capacity:
|
73
|
+
self._resize_matrix(self._capacity * 2)
|
74
|
+
|
75
|
+
index = self._vertex_count
|
76
|
+
self._vertex_to_index[vertex] = index
|
77
|
+
self._index_to_vertex[index] = vertex
|
78
|
+
self._vertex_count += 1
|
79
|
+
return index
|
80
|
+
|
81
|
+
return self._vertex_to_index[vertex]
|
82
|
+
|
83
|
+
def _get_vertex_by_index(self, index: int) -> Optional[str]:
|
84
|
+
"""Get vertex by index."""
|
85
|
+
return self._index_to_vertex.get(index)
|
86
|
+
|
87
|
+
# ============================================================================
|
88
|
+
# CORE EDGE OPERATIONS
|
89
|
+
# ============================================================================
|
90
|
+
|
91
|
+
def add_edge(self, from_node: Any, to_node: Any, **kwargs) -> None:
|
92
|
+
"""Add an edge between source and target vertices."""
|
93
|
+
source = str(from_node)
|
94
|
+
target = str(to_node)
|
95
|
+
|
96
|
+
# Validation
|
97
|
+
if not self.allow_self_loops and source == target:
|
98
|
+
raise ValueError("Self loops not allowed")
|
99
|
+
|
100
|
+
# Get vertex indices
|
101
|
+
source_idx = self._get_vertex_index(source)
|
102
|
+
target_idx = self._get_vertex_index(target)
|
103
|
+
|
104
|
+
# Create edge data
|
105
|
+
edge_data = {
|
106
|
+
'weight': kwargs.get('weight', self.default_weight),
|
107
|
+
'id': f"edge_{self._edge_id_counter}",
|
108
|
+
**kwargs
|
109
|
+
}
|
110
|
+
self._edge_id_counter += 1
|
111
|
+
|
112
|
+
# Add edge to matrix
|
113
|
+
if self._matrix[source_idx][target_idx] is None:
|
114
|
+
self._edge_count += 1
|
115
|
+
|
116
|
+
self._matrix[source_idx][target_idx] = edge_data
|
117
|
+
|
118
|
+
# Add reverse edge if undirected
|
119
|
+
if not self.is_directed and source != target:
|
120
|
+
if self._matrix[target_idx][source_idx] is None:
|
121
|
+
self._edge_count += 1
|
122
|
+
self._matrix[target_idx][source_idx] = edge_data
|
123
|
+
|
124
|
+
def remove_edge(self, from_node: Any, to_node: Any) -> bool:
|
125
|
+
"""Remove edge between vertices."""
|
126
|
+
source = str(from_node)
|
127
|
+
target = str(to_node)
|
128
|
+
|
129
|
+
if source not in self._vertex_to_index or target not in self._vertex_to_index:
|
130
|
+
return False
|
131
|
+
|
132
|
+
source_idx = self._vertex_to_index[source]
|
133
|
+
target_idx = self._vertex_to_index[target]
|
134
|
+
|
135
|
+
removed = False
|
136
|
+
|
137
|
+
# Remove edge from matrix
|
138
|
+
if self._matrix[source_idx][target_idx] is not None:
|
139
|
+
self._matrix[source_idx][target_idx] = None
|
140
|
+
self._edge_count -= 1
|
141
|
+
removed = True
|
142
|
+
|
143
|
+
# Remove reverse edge if undirected
|
144
|
+
if not self.is_directed and source != target:
|
145
|
+
if self._matrix[target_idx][source_idx] is not None:
|
146
|
+
self._matrix[target_idx][source_idx] = None
|
147
|
+
self._edge_count -= 1
|
148
|
+
|
149
|
+
return removed
|
150
|
+
|
151
|
+
def has_edge(self, from_node: Any, to_node: Any) -> bool:
|
152
|
+
"""Check if edge exists."""
|
153
|
+
source = str(from_node)
|
154
|
+
target = str(to_node)
|
155
|
+
|
156
|
+
if source not in self._vertex_to_index or target not in self._vertex_to_index:
|
157
|
+
return False
|
158
|
+
|
159
|
+
source_idx = self._vertex_to_index[source]
|
160
|
+
target_idx = self._vertex_to_index[target]
|
161
|
+
|
162
|
+
return self._matrix[source_idx][target_idx] is not None
|
163
|
+
|
164
|
+
def get_edge_count(self) -> int:
|
165
|
+
"""Get total number of edges."""
|
166
|
+
return self._edge_count
|
167
|
+
|
168
|
+
def get_vertex_count(self) -> int:
|
169
|
+
"""Get total number of vertices."""
|
170
|
+
return self._vertex_count
|
171
|
+
|
172
|
+
# ============================================================================
|
173
|
+
# GRAPH EDGE STRATEGY METHODS
|
174
|
+
# ============================================================================
|
175
|
+
|
176
|
+
def get_neighbors(self, node: Any) -> List[Any]:
|
177
|
+
"""Get all neighboring nodes."""
|
178
|
+
vertex = str(node)
|
179
|
+
if vertex not in self._vertex_to_index:
|
180
|
+
return []
|
181
|
+
|
182
|
+
vertex_idx = self._vertex_to_index[vertex]
|
183
|
+
neighbors = []
|
184
|
+
|
185
|
+
for i in range(self._capacity):
|
186
|
+
if self._matrix[vertex_idx][i] is not None:
|
187
|
+
neighbor = self._index_to_vertex.get(i)
|
188
|
+
if neighbor:
|
189
|
+
neighbors.append(neighbor)
|
190
|
+
|
191
|
+
return neighbors
|
192
|
+
|
193
|
+
def get_edge_weight(self, from_node: Any, to_node: Any) -> float:
|
194
|
+
"""Get edge weight."""
|
195
|
+
source = str(from_node)
|
196
|
+
target = str(to_node)
|
197
|
+
|
198
|
+
if source not in self._vertex_to_index or target not in self._vertex_to_index:
|
199
|
+
return 0.0
|
200
|
+
|
201
|
+
source_idx = self._vertex_to_index[source]
|
202
|
+
target_idx = self._vertex_to_index[target]
|
203
|
+
|
204
|
+
edge_data = self._matrix[source_idx][target_idx]
|
205
|
+
return edge_data.get('weight', self.default_weight) if edge_data else 0.0
|
206
|
+
|
207
|
+
def set_edge_weight(self, from_node: Any, to_node: Any, weight: float) -> None:
|
208
|
+
"""Set edge weight."""
|
209
|
+
source = str(from_node)
|
210
|
+
target = str(to_node)
|
211
|
+
|
212
|
+
if source not in self._vertex_to_index or target not in self._vertex_to_index:
|
213
|
+
raise ValueError(f"Edge {source} -> {target} not found")
|
214
|
+
|
215
|
+
source_idx = self._vertex_to_index[source]
|
216
|
+
target_idx = self._vertex_to_index[target]
|
217
|
+
|
218
|
+
if self._matrix[source_idx][target_idx] is None:
|
219
|
+
raise ValueError(f"Edge {source} -> {target} not found")
|
220
|
+
|
221
|
+
self._matrix[source_idx][target_idx]['weight'] = weight
|
222
|
+
|
223
|
+
def find_shortest_path(self, start: Any, end: Any) -> List[Any]:
|
224
|
+
"""Find shortest path between nodes."""
|
225
|
+
# TODO: Implement Dijkstra's algorithm
|
226
|
+
return []
|
227
|
+
|
228
|
+
def find_all_paths(self, start: Any, end: Any) -> List[List[Any]]:
|
229
|
+
"""Find all paths between nodes."""
|
230
|
+
# TODO: Implement DFS
|
231
|
+
return []
|
232
|
+
|
233
|
+
def get_connected_components(self) -> List[List[Any]]:
|
234
|
+
"""Get all connected components."""
|
235
|
+
# TODO: Implement connected components
|
236
|
+
return []
|
237
|
+
|
238
|
+
def is_connected(self, start: Any, end: Any) -> bool:
|
239
|
+
"""Check if two nodes are connected."""
|
240
|
+
# TODO: Implement connectivity check
|
241
|
+
return False
|
242
|
+
|
243
|
+
def get_degree(self, node: Any) -> int:
|
244
|
+
"""Get degree of node."""
|
245
|
+
return len(self.get_neighbors(node))
|
246
|
+
|
247
|
+
def is_cyclic(self) -> bool:
|
248
|
+
"""Check if graph contains cycles."""
|
249
|
+
# TODO: Implement cycle detection
|
250
|
+
return False
|
251
|
+
|
252
|
+
# ============================================================================
|
253
|
+
# MATRIX SPECIFIC OPERATIONS
|
254
|
+
# ============================================================================
|
255
|
+
|
256
|
+
def get_matrix(self) -> List[List[Optional[float]]]:
|
257
|
+
"""Get the adjacency matrix as a 2D list."""
|
258
|
+
matrix = []
|
259
|
+
for i in range(self._vertex_count):
|
260
|
+
row = []
|
261
|
+
for j in range(self._vertex_count):
|
262
|
+
edge_data = self._matrix[i][j]
|
263
|
+
weight = edge_data.get('weight', self.default_weight) if edge_data else 0.0
|
264
|
+
row.append(weight)
|
265
|
+
matrix.append(row)
|
266
|
+
return matrix
|
267
|
+
|
268
|
+
def get_binary_matrix(self) -> List[List[int]]:
|
269
|
+
"""Get the binary adjacency matrix."""
|
270
|
+
matrix = []
|
271
|
+
for i in range(self._vertex_count):
|
272
|
+
row = []
|
273
|
+
for j in range(self._vertex_count):
|
274
|
+
weight = 1 if self._matrix[i][j] is not None else 0
|
275
|
+
row.append(weight)
|
276
|
+
matrix.append(row)
|
277
|
+
return matrix
|
278
|
+
|
279
|
+
def set_matrix(self, matrix: List[List[Union[float, int, None]]], vertices: List[str]) -> None:
|
280
|
+
"""Set the adjacency matrix from a 2D list."""
|
281
|
+
# Clear existing data
|
282
|
+
self.clear()
|
283
|
+
|
284
|
+
# Set vertices
|
285
|
+
for vertex in vertices:
|
286
|
+
self._get_vertex_index(vertex)
|
287
|
+
|
288
|
+
# Set edges
|
289
|
+
for i, row in enumerate(matrix):
|
290
|
+
for j, weight in enumerate(row):
|
291
|
+
if weight is not None and weight != 0:
|
292
|
+
source = self._index_to_vertex[i]
|
293
|
+
target = self._index_to_vertex[j]
|
294
|
+
self.add_edge(source, target, weight=float(weight))
|
295
|
+
|
296
|
+
def matrix_multiply(self, other: 'xAdjMatrixStrategy') -> 'xAdjMatrixStrategy':
|
297
|
+
"""Multiply this matrix with another adjacency matrix."""
|
298
|
+
# TODO: Implement matrix multiplication
|
299
|
+
return self
|
300
|
+
|
301
|
+
def transpose(self) -> 'xAdjMatrixStrategy':
|
302
|
+
"""Get the transpose of the adjacency matrix."""
|
303
|
+
# TODO: Implement matrix transpose
|
304
|
+
return self
|
305
|
+
|
306
|
+
# ============================================================================
|
307
|
+
# ITERATION
|
308
|
+
# ============================================================================
|
309
|
+
|
310
|
+
def edges(self) -> Iterator[tuple[Any, Any, Dict[str, Any]]]:
|
311
|
+
"""Get all edges in the graph."""
|
312
|
+
for i in range(self._vertex_count):
|
313
|
+
for j in range(self._vertex_count):
|
314
|
+
if self._matrix[i][j] is not None:
|
315
|
+
source = self._index_to_vertex[i]
|
316
|
+
target = self._index_to_vertex[j]
|
317
|
+
yield (source, target, self._matrix[i][j])
|
318
|
+
|
319
|
+
def vertices(self) -> Iterator[Any]:
|
320
|
+
"""Get all vertices in the graph."""
|
321
|
+
return iter(self._vertex_to_index.keys())
|
322
|
+
|
323
|
+
# ============================================================================
|
324
|
+
# UTILITY METHODS
|
325
|
+
# ============================================================================
|
326
|
+
|
327
|
+
def clear(self) -> None:
|
328
|
+
"""Clear all edges and vertices."""
|
329
|
+
self._matrix = [[None for _ in range(self._capacity)] for _ in range(self._capacity)]
|
330
|
+
self._vertex_to_index.clear()
|
331
|
+
self._index_to_vertex.clear()
|
332
|
+
self._vertex_count = 0
|
333
|
+
self._edge_count = 0
|
334
|
+
self._edge_id_counter = 0
|
335
|
+
|
336
|
+
def add_vertex(self, vertex: str) -> None:
|
337
|
+
"""Add a vertex to the graph."""
|
338
|
+
self._get_vertex_index(vertex)
|
339
|
+
|
340
|
+
def remove_vertex(self, vertex: str) -> bool:
|
341
|
+
"""Remove a vertex and all its incident edges."""
|
342
|
+
if vertex not in self._vertex_to_index:
|
343
|
+
return False
|
344
|
+
|
345
|
+
vertex_idx = self._vertex_to_index[vertex]
|
346
|
+
|
347
|
+
# Remove all edges incident to this vertex
|
348
|
+
for i in range(self._capacity):
|
349
|
+
if self._matrix[vertex_idx][i] is not None:
|
350
|
+
self._matrix[vertex_idx][i] = None
|
351
|
+
self._edge_count -= 1
|
352
|
+
if self._matrix[i][vertex_idx] is not None:
|
353
|
+
self._matrix[i][vertex_idx] = None
|
354
|
+
self._edge_count -= 1
|
355
|
+
|
356
|
+
# Remove vertex from mappings
|
357
|
+
del self._vertex_to_index[vertex]
|
358
|
+
del self._index_to_vertex[vertex_idx]
|
359
|
+
self._vertex_count -= 1
|
360
|
+
|
361
|
+
return True
|
362
|
+
|
363
|
+
# ============================================================================
|
364
|
+
# PERFORMANCE CHARACTERISTICS
|
365
|
+
# ============================================================================
|
366
|
+
|
367
|
+
@property
|
368
|
+
def backend_info(self) -> Dict[str, Any]:
|
369
|
+
"""Get backend implementation info."""
|
370
|
+
return {
|
371
|
+
'strategy': 'ADJ_MATRIX',
|
372
|
+
'backend': '2D matrix',
|
373
|
+
'complexity': {
|
374
|
+
'add_edge': 'O(1)',
|
375
|
+
'remove_edge': 'O(1)',
|
376
|
+
'has_edge': 'O(1)',
|
377
|
+
'get_neighbors': 'O(V)',
|
378
|
+
'space': 'O(V²)'
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
@property
|
383
|
+
def metrics(self) -> Dict[str, Any]:
|
384
|
+
"""Get performance metrics."""
|
385
|
+
return {
|
386
|
+
'vertices': self._vertex_count,
|
387
|
+
'edges': self._edge_count,
|
388
|
+
'capacity': self._capacity,
|
389
|
+
'directed': self.is_directed,
|
390
|
+
'density': f"{(self._edge_count / max(1, self._vertex_count * (self._vertex_count - 1))) * 100:.1f}%"
|
391
|
+
}
|