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,353 @@
|
|
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_edge import aEdgeStrategy
|
11
|
+
from ...types import EdgeMode, EdgeTrait
|
12
|
+
|
13
|
+
|
14
|
+
class xAdjListStrategy(aEdgeStrategy):
|
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__(EdgeMode.ADJ_LIST, traits, **options)
|
25
|
+
|
26
|
+
self.is_directed = options.get('directed', True)
|
27
|
+
self.allow_self_loops = options.get('self_loops', True)
|
28
|
+
self.allow_multi_edges = options.get('multi_edges', False)
|
29
|
+
|
30
|
+
# Core storage: vertex -> list of (neighbor, edge_data)
|
31
|
+
self._outgoing: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list)
|
32
|
+
self._incoming: Dict[str, List[Tuple[str, Dict[str, Any]]]] = defaultdict(list) if self.is_directed else None
|
33
|
+
|
34
|
+
# Vertex set for fast membership testing
|
35
|
+
self._vertices: Set[str] = set()
|
36
|
+
|
37
|
+
# Edge properties storage
|
38
|
+
self._edge_count = 0
|
39
|
+
self._edge_id_counter = 0
|
40
|
+
|
41
|
+
def get_supported_traits(self) -> EdgeTrait:
|
42
|
+
"""Get the traits supported by the adjacency list strategy."""
|
43
|
+
return (EdgeTrait.SPARSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.MULTI)
|
44
|
+
|
45
|
+
# ============================================================================
|
46
|
+
# CORE EDGE OPERATIONS
|
47
|
+
# ============================================================================
|
48
|
+
|
49
|
+
def add_edge(self, source: str, target: str, **properties) -> str:
|
50
|
+
"""Add an edge between source and target vertices."""
|
51
|
+
# Validate self-loops
|
52
|
+
if source == target and not self.allow_self_loops:
|
53
|
+
raise ValueError(f"Self-loops not allowed: {source} -> {target}")
|
54
|
+
|
55
|
+
# Check for existing edge if multi-edges not allowed
|
56
|
+
if not self.allow_multi_edges and self.has_edge(source, target):
|
57
|
+
raise ValueError(f"Multi-edges not allowed: {source} -> {target}")
|
58
|
+
|
59
|
+
# Generate edge ID
|
60
|
+
edge_id = f"edge_{self._edge_id_counter}"
|
61
|
+
self._edge_id_counter += 1
|
62
|
+
|
63
|
+
# Create edge data
|
64
|
+
edge_data = {
|
65
|
+
'id': edge_id,
|
66
|
+
'source': source,
|
67
|
+
'target': target,
|
68
|
+
'properties': properties.copy()
|
69
|
+
}
|
70
|
+
|
71
|
+
# Add vertices to vertex set
|
72
|
+
self._vertices.add(source)
|
73
|
+
self._vertices.add(target)
|
74
|
+
|
75
|
+
# Add to outgoing adjacency list
|
76
|
+
self._outgoing[source].append((target, edge_data))
|
77
|
+
|
78
|
+
# Add to incoming adjacency list (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 graphs, add reverse edge (unless it's a self-loop)
|
83
|
+
if source != target:
|
84
|
+
self._outgoing[target].append((source, edge_data))
|
85
|
+
|
86
|
+
self._edge_count += 1
|
87
|
+
return edge_id
|
88
|
+
|
89
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
90
|
+
"""Remove edge(s) between source and target."""
|
91
|
+
if source not in self._outgoing:
|
92
|
+
return False
|
93
|
+
|
94
|
+
removed = False
|
95
|
+
|
96
|
+
# Remove from outgoing list
|
97
|
+
original_length = len(self._outgoing[source])
|
98
|
+
if edge_id:
|
99
|
+
# Remove specific edge by ID
|
100
|
+
self._outgoing[source] = [
|
101
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
102
|
+
if not (neighbor == target and data['id'] == edge_id)
|
103
|
+
]
|
104
|
+
else:
|
105
|
+
# Remove all edges to target
|
106
|
+
self._outgoing[source] = [
|
107
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
108
|
+
if neighbor != target
|
109
|
+
]
|
110
|
+
|
111
|
+
removed = len(self._outgoing[source]) < original_length
|
112
|
+
|
113
|
+
if removed:
|
114
|
+
self._edge_count -= (original_length - len(self._outgoing[source]))
|
115
|
+
|
116
|
+
# Remove from incoming list (if directed)
|
117
|
+
if self.is_directed and self._incoming is not None and target in self._incoming:
|
118
|
+
if edge_id:
|
119
|
+
self._incoming[target] = [
|
120
|
+
(neighbor, data) for neighbor, data in self._incoming[target]
|
121
|
+
if not (neighbor == source and data['id'] == edge_id)
|
122
|
+
]
|
123
|
+
else:
|
124
|
+
self._incoming[target] = [
|
125
|
+
(neighbor, data) for neighbor, data in self._incoming[target]
|
126
|
+
if neighbor != source
|
127
|
+
]
|
128
|
+
elif not self.is_directed and source != target:
|
129
|
+
# For undirected graphs, remove reverse edge
|
130
|
+
if target in self._outgoing:
|
131
|
+
if edge_id:
|
132
|
+
self._outgoing[target] = [
|
133
|
+
(neighbor, data) for neighbor, data in self._outgoing[target]
|
134
|
+
if not (neighbor == source and data['id'] == edge_id)
|
135
|
+
]
|
136
|
+
else:
|
137
|
+
self._outgoing[target] = [
|
138
|
+
(neighbor, data) for neighbor, data in self._outgoing[target]
|
139
|
+
if neighbor != source
|
140
|
+
]
|
141
|
+
|
142
|
+
return removed
|
143
|
+
|
144
|
+
def has_edge(self, source: str, target: str) -> bool:
|
145
|
+
"""Check if edge exists between source and target."""
|
146
|
+
if source not in self._outgoing:
|
147
|
+
return False
|
148
|
+
|
149
|
+
return any(neighbor == target for neighbor, _ in self._outgoing[source])
|
150
|
+
|
151
|
+
def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
|
152
|
+
"""Get edge data between source and target."""
|
153
|
+
if source not in self._outgoing:
|
154
|
+
return None
|
155
|
+
|
156
|
+
for neighbor, data in self._outgoing[source]:
|
157
|
+
if neighbor == target:
|
158
|
+
return data
|
159
|
+
|
160
|
+
return None
|
161
|
+
|
162
|
+
def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
|
163
|
+
"""Get neighbors of a vertex."""
|
164
|
+
if direction == 'out':
|
165
|
+
if vertex in self._outgoing:
|
166
|
+
for neighbor, _ in self._outgoing[vertex]:
|
167
|
+
yield neighbor
|
168
|
+
elif direction == 'in':
|
169
|
+
if self.is_directed and self._incoming is not None and vertex in self._incoming:
|
170
|
+
for neighbor, _ in self._incoming[vertex]:
|
171
|
+
yield neighbor
|
172
|
+
elif not self.is_directed:
|
173
|
+
# For undirected graphs, incoming = outgoing
|
174
|
+
if vertex in self._outgoing:
|
175
|
+
for neighbor, _ in self._outgoing[vertex]:
|
176
|
+
yield neighbor
|
177
|
+
elif direction == 'both':
|
178
|
+
# Get all neighbors (both in and out)
|
179
|
+
seen = set()
|
180
|
+
for neighbor in self.neighbors(vertex, 'out'):
|
181
|
+
if neighbor not in seen:
|
182
|
+
seen.add(neighbor)
|
183
|
+
yield neighbor
|
184
|
+
for neighbor in self.neighbors(vertex, 'in'):
|
185
|
+
if neighbor not in seen:
|
186
|
+
seen.add(neighbor)
|
187
|
+
yield neighbor
|
188
|
+
|
189
|
+
def degree(self, vertex: str, direction: str = 'out') -> int:
|
190
|
+
"""Get degree of a vertex."""
|
191
|
+
if direction == 'out':
|
192
|
+
return len(self._outgoing.get(vertex, []))
|
193
|
+
elif direction == 'in':
|
194
|
+
if self.is_directed and self._incoming is not None:
|
195
|
+
return len(self._incoming.get(vertex, []))
|
196
|
+
elif not self.is_directed:
|
197
|
+
return len(self._outgoing.get(vertex, []))
|
198
|
+
else:
|
199
|
+
return 0
|
200
|
+
elif direction == 'both':
|
201
|
+
out_degree = self.degree(vertex, 'out')
|
202
|
+
in_degree = self.degree(vertex, 'in')
|
203
|
+
# For undirected graphs, avoid double counting
|
204
|
+
return out_degree if not self.is_directed else out_degree + in_degree
|
205
|
+
|
206
|
+
def edges(self, data: bool = False) -> Iterator[tuple]:
|
207
|
+
"""Get all edges in the graph."""
|
208
|
+
seen_edges = set()
|
209
|
+
|
210
|
+
for source, adj_list in self._outgoing.items():
|
211
|
+
for target, edge_data in adj_list:
|
212
|
+
edge_key = (source, target, edge_data['id'])
|
213
|
+
|
214
|
+
if edge_key not in seen_edges:
|
215
|
+
seen_edges.add(edge_key)
|
216
|
+
|
217
|
+
if data:
|
218
|
+
yield (source, target, edge_data)
|
219
|
+
else:
|
220
|
+
yield (source, target)
|
221
|
+
|
222
|
+
def vertices(self) -> Iterator[str]:
|
223
|
+
"""Get all vertices in the graph."""
|
224
|
+
return iter(self._vertices)
|
225
|
+
|
226
|
+
def __len__(self) -> int:
|
227
|
+
"""Get the number of edges."""
|
228
|
+
return self._edge_count
|
229
|
+
|
230
|
+
def vertex_count(self) -> int:
|
231
|
+
"""Get the number of vertices."""
|
232
|
+
return len(self._vertices)
|
233
|
+
|
234
|
+
def clear(self) -> None:
|
235
|
+
"""Clear all edges and vertices."""
|
236
|
+
self._outgoing.clear()
|
237
|
+
if self._incoming is not None:
|
238
|
+
self._incoming.clear()
|
239
|
+
self._vertices.clear()
|
240
|
+
self._edge_count = 0
|
241
|
+
self._edge_id_counter = 0
|
242
|
+
|
243
|
+
def add_vertex(self, vertex: str) -> None:
|
244
|
+
"""Add a vertex to the graph."""
|
245
|
+
self._vertices.add(vertex)
|
246
|
+
# Initialize adjacency lists if not present
|
247
|
+
if vertex not in self._outgoing:
|
248
|
+
self._outgoing[vertex] = []
|
249
|
+
if self.is_directed and self._incoming is not None and vertex not in self._incoming:
|
250
|
+
self._incoming[vertex] = []
|
251
|
+
|
252
|
+
def remove_vertex(self, vertex: str) -> bool:
|
253
|
+
"""Remove a vertex and all its edges."""
|
254
|
+
if vertex not in self._vertices:
|
255
|
+
return False
|
256
|
+
|
257
|
+
# Remove all outgoing edges
|
258
|
+
edges_removed = len(self._outgoing.get(vertex, []))
|
259
|
+
self._edge_count -= edges_removed
|
260
|
+
|
261
|
+
# Remove all incoming edges
|
262
|
+
for source in list(self._outgoing.keys()):
|
263
|
+
if source != vertex:
|
264
|
+
original_length = len(self._outgoing[source])
|
265
|
+
self._outgoing[source] = [
|
266
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
267
|
+
if neighbor != vertex
|
268
|
+
]
|
269
|
+
self._edge_count -= (original_length - len(self._outgoing[source]))
|
270
|
+
|
271
|
+
# Clean up adjacency lists
|
272
|
+
if vertex in self._outgoing:
|
273
|
+
del self._outgoing[vertex]
|
274
|
+
if self._incoming is not None and vertex in self._incoming:
|
275
|
+
del self._incoming[vertex]
|
276
|
+
|
277
|
+
# Remove from vertex set
|
278
|
+
self._vertices.remove(vertex)
|
279
|
+
|
280
|
+
return True
|
281
|
+
|
282
|
+
# ============================================================================
|
283
|
+
# ADVANCED OPERATIONS
|
284
|
+
# ============================================================================
|
285
|
+
|
286
|
+
def get_subgraph(self, vertices: Set[str]) -> 'xAdjListStrategy':
|
287
|
+
"""Extract subgraph containing only specified vertices."""
|
288
|
+
subgraph = xAdjListStrategy(
|
289
|
+
traits=self._traits,
|
290
|
+
directed=self.is_directed,
|
291
|
+
self_loops=self.allow_self_loops,
|
292
|
+
multi_edges=self.allow_multi_edges
|
293
|
+
)
|
294
|
+
|
295
|
+
# Add vertices
|
296
|
+
for vertex in vertices:
|
297
|
+
if vertex in self._vertices:
|
298
|
+
subgraph.add_vertex(vertex)
|
299
|
+
|
300
|
+
# Add edges
|
301
|
+
for source, target, edge_data in self.edges(data=True):
|
302
|
+
if source in vertices and target in vertices:
|
303
|
+
subgraph.add_edge(source, target, **edge_data['properties'])
|
304
|
+
|
305
|
+
return subgraph
|
306
|
+
|
307
|
+
def get_edge_list(self) -> List[Tuple[str, str, Dict[str, Any]]]:
|
308
|
+
"""Get all edges as a list."""
|
309
|
+
return list(self.edges(data=True))
|
310
|
+
|
311
|
+
def get_adjacency_dict(self) -> Dict[str, List[str]]:
|
312
|
+
"""Get adjacency representation as a dictionary."""
|
313
|
+
return {
|
314
|
+
vertex: [neighbor for neighbor, _ in adj_list]
|
315
|
+
for vertex, adj_list in self._outgoing.items()
|
316
|
+
}
|
317
|
+
|
318
|
+
# ============================================================================
|
319
|
+
# PERFORMANCE CHARACTERISTICS
|
320
|
+
# ============================================================================
|
321
|
+
|
322
|
+
@property
|
323
|
+
def backend_info(self) -> Dict[str, Any]:
|
324
|
+
"""Get backend implementation info."""
|
325
|
+
return {
|
326
|
+
'strategy': 'adjacency_list',
|
327
|
+
'backend': 'Python defaultdict + lists',
|
328
|
+
'directed': self.is_directed,
|
329
|
+
'multi_edges': self.allow_multi_edges,
|
330
|
+
'self_loops': self.allow_self_loops,
|
331
|
+
'complexity': {
|
332
|
+
'add_edge': 'O(1)',
|
333
|
+
'remove_edge': 'O(degree)',
|
334
|
+
'has_edge': 'O(degree)',
|
335
|
+
'neighbors': 'O(degree)',
|
336
|
+
'space': 'O(V + E)'
|
337
|
+
}
|
338
|
+
}
|
339
|
+
|
340
|
+
@property
|
341
|
+
def metrics(self) -> Dict[str, Any]:
|
342
|
+
"""Get performance metrics."""
|
343
|
+
avg_degree = self._edge_count / max(1, len(self._vertices)) if self._vertices else 0
|
344
|
+
density = self._edge_count / max(1, len(self._vertices) * (len(self._vertices) - 1)) if len(self._vertices) > 1 else 0
|
345
|
+
|
346
|
+
return {
|
347
|
+
'vertices': len(self._vertices),
|
348
|
+
'edges': self._edge_count,
|
349
|
+
'average_degree': round(avg_degree, 2),
|
350
|
+
'density': round(density, 4),
|
351
|
+
'memory_usage': f"{len(self._vertices) * 48 + self._edge_count * 32} bytes (estimated)",
|
352
|
+
'sparsity': f"{(1 - density) * 100:.1f}%"
|
353
|
+
}
|