exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.23__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 (248) hide show
  1. exonware/__init__.py +1 -1
  2. exonware/xwnode/__init__.py +18 -5
  3. exonware/xwnode/add_strategy_types.py +165 -0
  4. exonware/xwnode/common/__init__.py +1 -1
  5. exonware/xwnode/common/graph/__init__.py +30 -0
  6. exonware/xwnode/common/graph/caching.py +131 -0
  7. exonware/xwnode/common/graph/contracts.py +100 -0
  8. exonware/xwnode/common/graph/errors.py +44 -0
  9. exonware/xwnode/common/graph/indexing.py +260 -0
  10. exonware/xwnode/common/graph/manager.py +568 -0
  11. exonware/xwnode/common/management/__init__.py +3 -5
  12. exonware/xwnode/common/management/manager.py +2 -2
  13. exonware/xwnode/common/management/migration.py +3 -3
  14. exonware/xwnode/common/monitoring/__init__.py +3 -5
  15. exonware/xwnode/common/monitoring/metrics.py +6 -2
  16. exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
  17. exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
  18. exonware/xwnode/common/patterns/__init__.py +3 -5
  19. exonware/xwnode/common/patterns/flyweight.py +5 -1
  20. exonware/xwnode/common/patterns/registry.py +202 -183
  21. exonware/xwnode/common/utils/__init__.py +25 -11
  22. exonware/xwnode/common/utils/simple.py +1 -1
  23. exonware/xwnode/config.py +3 -8
  24. exonware/xwnode/contracts.py +4 -105
  25. exonware/xwnode/defs.py +413 -159
  26. exonware/xwnode/edges/strategies/__init__.py +86 -4
  27. exonware/xwnode/edges/strategies/_base_edge.py +2 -2
  28. exonware/xwnode/edges/strategies/adj_list.py +287 -121
  29. exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
  30. exonware/xwnode/edges/strategies/base.py +1 -1
  31. exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
  32. exonware/xwnode/edges/strategies/bitemporal.py +520 -0
  33. exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
  34. exonware/xwnode/edges/strategies/bv_graph.py +664 -0
  35. exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
  36. exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
  37. exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
  38. exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
  39. exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
  40. exonware/xwnode/edges/strategies/edge_list.py +168 -0
  41. exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
  42. exonware/xwnode/edges/strategies/euler_tour.py +560 -0
  43. exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
  44. exonware/xwnode/edges/strategies/graphblas.py +449 -0
  45. exonware/xwnode/edges/strategies/hnsw.py +637 -0
  46. exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
  47. exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
  48. exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
  49. exonware/xwnode/edges/strategies/k2_tree.py +613 -0
  50. exonware/xwnode/edges/strategies/link_cut.py +626 -0
  51. exonware/xwnode/edges/strategies/multiplex.py +532 -0
  52. exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
  53. exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
  54. exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
  55. exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
  56. exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
  57. exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
  58. exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
  59. exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
  60. exonware/xwnode/errors.py +3 -6
  61. exonware/xwnode/facade.py +20 -20
  62. exonware/xwnode/nodes/strategies/__init__.py +29 -9
  63. exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
  64. exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
  65. exonware/xwnode/nodes/strategies/array_list.py +36 -3
  66. exonware/xwnode/nodes/strategies/art.py +581 -0
  67. exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
  68. exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
  69. exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
  70. exonware/xwnode/nodes/strategies/base.py +469 -98
  71. exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
  72. exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
  73. exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
  74. exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
  75. exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
  76. exonware/xwnode/nodes/strategies/contracts.py +1 -1
  77. exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
  78. exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
  79. exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
  80. exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
  81. exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
  82. exonware/xwnode/nodes/strategies/dawg.py +876 -0
  83. exonware/xwnode/nodes/strategies/deque.py +321 -153
  84. exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
  85. exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
  86. exonware/xwnode/nodes/strategies/hamt.py +403 -0
  87. exonware/xwnode/nodes/strategies/hash_map.py +354 -67
  88. exonware/xwnode/nodes/strategies/heap.py +105 -5
  89. exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
  90. exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
  91. exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
  92. exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
  93. exonware/xwnode/nodes/strategies/learned_index.py +533 -0
  94. exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
  95. exonware/xwnode/nodes/strategies/linked_list.py +316 -119
  96. exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
  97. exonware/xwnode/nodes/strategies/masstree.py +130 -0
  98. exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
  99. exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
  100. exonware/xwnode/nodes/strategies/queue.py +249 -120
  101. exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
  102. exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
  103. exonware/xwnode/nodes/strategies/rope.py +717 -0
  104. exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
  105. exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
  106. exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
  107. exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
  108. exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
  109. exonware/xwnode/nodes/strategies/stack.py +244 -112
  110. exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
  111. exonware/xwnode/nodes/strategies/t_tree.py +94 -0
  112. exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
  113. exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
  114. exonware/xwnode/nodes/strategies/trie.py +153 -9
  115. exonware/xwnode/nodes/strategies/union_find.py +111 -5
  116. exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
  117. exonware/xwnode/strategies/__init__.py +5 -51
  118. exonware/xwnode/version.py +3 -3
  119. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
  120. exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
  121. exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
  122. exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
  123. exonware/xwnode/nodes/strategies/_base_node.py +0 -307
  124. exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
  125. exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
  126. exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
  127. exonware/xwnode/nodes/strategies/node_heap.py +0 -196
  128. exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
  129. exonware/xwnode/nodes/strategies/node_trie.py +0 -257
  130. exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
  131. exonware/xwnode/queries/executors/__init__.py +0 -47
  132. exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
  133. exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
  134. exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
  135. exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
  136. exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
  137. exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
  138. exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
  139. exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
  140. exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
  141. exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
  142. exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
  143. exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
  144. exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
  145. exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
  146. exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
  147. exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
  148. exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
  149. exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
  150. exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
  151. exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
  152. exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
  153. exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
  154. exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
  155. exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
  156. exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
  157. exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
  158. exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
  159. exonware/xwnode/queries/executors/array/__init__.py +0 -9
  160. exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
  161. exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
  162. exonware/xwnode/queries/executors/base.py +0 -257
  163. exonware/xwnode/queries/executors/capability_checker.py +0 -204
  164. exonware/xwnode/queries/executors/contracts.py +0 -166
  165. exonware/xwnode/queries/executors/core/__init__.py +0 -17
  166. exonware/xwnode/queries/executors/core/create_executor.py +0 -96
  167. exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
  168. exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
  169. exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
  170. exonware/xwnode/queries/executors/core/select_executor.py +0 -152
  171. exonware/xwnode/queries/executors/core/update_executor.py +0 -102
  172. exonware/xwnode/queries/executors/data/__init__.py +0 -13
  173. exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
  174. exonware/xwnode/queries/executors/data/load_executor.py +0 -50
  175. exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
  176. exonware/xwnode/queries/executors/data/store_executor.py +0 -50
  177. exonware/xwnode/queries/executors/defs.py +0 -93
  178. exonware/xwnode/queries/executors/engine.py +0 -221
  179. exonware/xwnode/queries/executors/errors.py +0 -68
  180. exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
  181. exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
  182. exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
  183. exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
  184. exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
  185. exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
  186. exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
  187. exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
  188. exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
  189. exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
  190. exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
  191. exonware/xwnode/queries/executors/graph/__init__.py +0 -15
  192. exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
  193. exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
  194. exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
  195. exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
  196. exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
  197. exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
  198. exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
  199. exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
  200. exonware/xwnode/queries/executors/projection/__init__.py +0 -9
  201. exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
  202. exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
  203. exonware/xwnode/queries/executors/registry.py +0 -173
  204. exonware/xwnode/queries/parsers/__init__.py +0 -26
  205. exonware/xwnode/queries/parsers/base.py +0 -86
  206. exonware/xwnode/queries/parsers/contracts.py +0 -46
  207. exonware/xwnode/queries/parsers/errors.py +0 -53
  208. exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
  209. exonware/xwnode/queries/strategies/__init__.py +0 -24
  210. exonware/xwnode/queries/strategies/base.py +0 -236
  211. exonware/xwnode/queries/strategies/cql.py +0 -201
  212. exonware/xwnode/queries/strategies/cypher.py +0 -181
  213. exonware/xwnode/queries/strategies/datalog.py +0 -70
  214. exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
  215. exonware/xwnode/queries/strategies/eql.py +0 -70
  216. exonware/xwnode/queries/strategies/flux.py +0 -70
  217. exonware/xwnode/queries/strategies/gql.py +0 -70
  218. exonware/xwnode/queries/strategies/graphql.py +0 -240
  219. exonware/xwnode/queries/strategies/gremlin.py +0 -181
  220. exonware/xwnode/queries/strategies/hiveql.py +0 -214
  221. exonware/xwnode/queries/strategies/hql.py +0 -70
  222. exonware/xwnode/queries/strategies/jmespath.py +0 -219
  223. exonware/xwnode/queries/strategies/jq.py +0 -66
  224. exonware/xwnode/queries/strategies/json_query.py +0 -66
  225. exonware/xwnode/queries/strategies/jsoniq.py +0 -248
  226. exonware/xwnode/queries/strategies/kql.py +0 -70
  227. exonware/xwnode/queries/strategies/linq.py +0 -238
  228. exonware/xwnode/queries/strategies/logql.py +0 -70
  229. exonware/xwnode/queries/strategies/mql.py +0 -68
  230. exonware/xwnode/queries/strategies/n1ql.py +0 -210
  231. exonware/xwnode/queries/strategies/partiql.py +0 -70
  232. exonware/xwnode/queries/strategies/pig.py +0 -215
  233. exonware/xwnode/queries/strategies/promql.py +0 -70
  234. exonware/xwnode/queries/strategies/sparql.py +0 -220
  235. exonware/xwnode/queries/strategies/sql.py +0 -275
  236. exonware/xwnode/queries/strategies/xml_query.py +0 -66
  237. exonware/xwnode/queries/strategies/xpath.py +0 -223
  238. exonware/xwnode/queries/strategies/xquery.py +0 -258
  239. exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
  240. exonware/xwnode/queries/strategies/xwquery.py +0 -456
  241. exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
  242. /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
  243. /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
  244. /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
  245. /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
  246. /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
  247. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
  248. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,26 @@
1
1
  """
2
+ #exonware/xwnode/src/exonware/xwnode/nodes/strategies/adjacency_list.py
3
+
2
4
  Adjacency List Strategy Implementation
3
5
 
4
- Implements graph operations using adjacency list representation.
6
+ Production-grade graph representation using adjacency lists.
7
+
8
+ Best Practices Implemented:
9
+ - Dictionary of lists for O(1) neighbor access
10
+ - Support for directed and undirected graphs
11
+ - Weighted edges with efficient storage
12
+ - Industry-standard graph algorithms (DFS, BFS, topological sort)
13
+ - Proper graph semantics following CLRS and NetworkX patterns
5
14
 
6
15
  Company: eXonware.com
7
16
  Author: Eng. Muhammad AlShehri
8
17
  Email: connect@exonware.com
9
- Version: 0.0.1.22
10
- Generation Date: 07-Sep-2025
18
+ Version: 0.0.1.23
19
+ Generation Date: October 12, 2025
11
20
  """
12
21
 
13
- from typing import Any, Iterator, List, Optional, Dict, Union, Set, Tuple
14
- from collections import defaultdict
22
+ from typing import Any, Iterator, List, Optional, Dict, Set, Tuple, Callable
23
+ from collections import defaultdict, deque
15
24
  from .base import ANodeGraphStrategy
16
25
  from .contracts import NodeType
17
26
  from ...defs import NodeMode, NodeTrait
@@ -19,254 +28,718 @@ from ...defs import NodeMode, NodeTrait
19
28
 
20
29
  class AdjacencyListStrategy(ANodeGraphStrategy):
21
30
  """
22
- Adjacency List node strategy for graph operations.
31
+ Production-grade Adjacency List graph strategy.
32
+
33
+ Optimized for:
34
+ - Social networks (followers, friends, connections)
35
+ - Web graphs (links, citations, dependencies)
36
+ - Routing algorithms (Dijkstra, A*, Bellman-Ford)
37
+ - Dependency graphs (build systems, package managers)
38
+ - Recommendation systems (user-item graphs)
39
+ - Network analysis (PageRank, community detection)
40
+
41
+ Format: Dictionary of Lists
42
+ - Best for: Sparse graphs, dynamic graphs
43
+ - Storage: node -> list of (neighbor, weight) tuples
44
+ - Space: O(V + E) where V=vertices, E=edges
45
+ - Operations: O(degree) neighbor queries
46
+
47
+ Performance:
48
+ - Add vertex: O(1)
49
+ - Add edge: O(1)
50
+ - Remove edge: O(degree)
51
+ - Get neighbors: O(1) to O(degree)
52
+ - DFS: O(V + E)
53
+ - BFS: O(V + E)
54
+ - Topological sort: O(V + E)
55
+
56
+ Security:
57
+ - Vertex existence validation
58
+ - Cycle detection for safety
59
+ - Safe edge weight handling
23
60
 
24
- Uses adjacency list representation for efficient neighbor queries
25
- and edge oper
61
+ Follows eXonware Priorities:
62
+ 1. Security: Input validation, safe traversal
63
+ 2. Usability: Standard graph operations interface
64
+ 3. Maintainability: Clean adjacency list implementation
65
+ 4. Performance: O(V + E) algorithms, O(1) neighbor access
66
+ 5. Extensibility: Easy to add custom graph algorithms
67
+ """
26
68
 
27
69
  # Strategy type classification
28
70
  STRATEGY_TYPE = NodeType.GRAPH
29
- ations in sparse graphs.
30
- """
31
71
 
32
- def __init__(self):
33
- """Initialize an empty adjacency list."""
34
- super().__init__()
35
- self._adj_list: Dict[str, List[Tuple[str, float]]] = defaultdict(list) # node -> [(neighbor, weight)]
36
- self._nodes: Dict[str, Any] = {} # node -> data
37
- self._mode = NodeMode.ADJACENCY_LIST
38
- self._traits = {NodeTrait.GRAPH, NodeTrait.SPARSE, NodeTrait.FAST_NEIGHBORS}
39
-
40
- def insert(self, key: str, value: Any) -> None:
41
- """Insert a node into the graph."""
42
- self._nodes[key] = value
43
- if key not in self._adj_list:
44
- self._adj_list[key] = []
45
-
46
- def find(self, key: str) -> Optional[Any]:
47
- """Find a node in the graph."""
48
- return self._nodes.get(key)
49
-
50
- def delete(self, key: str) -> bool:
51
- """Delete a node and all its edges."""
52
- if key not in self._nodes:
53
- return False
72
+ __slots__ = ('_adj_list', '_nodes', '_is_directed', '_edge_count')
73
+
74
+ def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
75
+ """
76
+ Initialize an empty adjacency list.
54
77
 
55
- # Remove node
56
- del self._nodes[key]
78
+ Args:
79
+ traits: Additional node traits
80
+ **options:
81
+ is_directed: True for directed graph, False for undirected
82
+ initial_vertices: Optional list of initial vertices
83
+ initial_edges: Optional list of (from, to, weight) tuples
84
+ """
85
+ super().__init__(
86
+ NodeMode.ADJACENCY_LIST,
87
+ traits | NodeTrait.GRAPH | NodeTrait.SPARSE | NodeTrait.FAST_NEIGHBORS,
88
+ **options
89
+ )
57
90
 
58
- # Remove all edges to this node
59
- for node in self._adj_list:
60
- self._adj_list[node] = [(neighbor, weight) for neighbor, weight in self._adj_list[node] if neighbor != key]
91
+ self._is_directed: bool = options.get('is_directed', True)
92
+ self._adj_list: Dict[str, List[Tuple[str, float]]] = defaultdict(list)
93
+ self._nodes: Dict[str, Any] = {}
94
+ self._edge_count = 0
61
95
 
62
- # Remove node's adjacency list
63
- if key in self._adj_list:
64
- del self._adj_list[key]
96
+ # Initialize vertices if provided
97
+ for vertex in options.get('initial_vertices', []):
98
+ self.add_vertex(vertex)
65
99
 
66
- return True
100
+ # Initialize edges if provided
101
+ for edge in options.get('initial_edges', []):
102
+ if len(edge) == 2:
103
+ self.add_edge(edge[0], edge[1])
104
+ elif len(edge) == 3:
105
+ self.add_edge(edge[0], edge[1], edge[2])
67
106
 
68
- def size(self) -> int:
69
- """Get the number of nodes in the graph."""
70
- return len(self._nodes)
107
+ def get_supported_traits(self) -> NodeTrait:
108
+ """Get the traits supported by the adjacency list strategy."""
109
+ return NodeTrait.GRAPH | NodeTrait.SPARSE | NodeTrait.FAST_NEIGHBORS
71
110
 
72
- def to_native(self) -> Dict[str, Any]:
73
- """Convert graph to native dictionary format."""
74
- return {
75
- 'nodes': self._nodes,
76
- 'edges': {node: neighbors for node, neighbors in self._adj_list.items() if neighbors}
77
- }
111
+ # ============================================================================
112
+ # CORE GRAPH OPERATIONS (Industry Standard)
113
+ # ============================================================================
78
114
 
79
- def from_native(self, data: Dict[str, Any]) -> None:
80
- """Load graph from native dictionary format."""
81
- self._nodes = data.get('nodes', {})
82
- edges = data.get('edges', {})
115
+ def add_vertex(self, vertex: str, data: Any = None) -> None:
116
+ """
117
+ Add a vertex to the graph.
83
118
 
84
- self._adj_list.clear()
85
- for node, neighbors in edges.items():
86
- self._adj_list[node] = neighbors
87
-
88
- def add_edge(self, from_node: str, to_node: str, weight: float = 1.0) -> None:
89
- """Add an edge between two nodes."""
90
- if from_node not in self._nodes:
91
- self._nodes[from_node] = None
92
- if to_node not in self._nodes:
93
- self._nodes[to_node] = None
94
-
95
- # Add edge (avoid duplicates)
96
- neighbors = self._adj_list[from_node]
119
+ Time: O(1)
120
+ Space: O(1)
121
+ """
122
+ if vertex not in self._nodes:
123
+ self._nodes[vertex] = data
124
+ if vertex not in self._adj_list:
125
+ self._adj_list[vertex] = []
126
+
127
+ def add_edge(self, from_vertex: str, to_vertex: str, weight: float = 1.0) -> None:
128
+ """
129
+ Add an edge between two vertices.
130
+
131
+ Time: O(1) amortized
132
+ Space: O(1)
133
+
134
+ Note: Automatically creates vertices if they don't exist
135
+ """
136
+ # Ensure vertices exist
137
+ self.add_vertex(from_vertex)
138
+ self.add_vertex(to_vertex)
139
+
140
+ # Check if edge already exists (avoid duplicates)
141
+ neighbors = self._adj_list[from_vertex]
97
142
  for i, (neighbor, _) in enumerate(neighbors):
98
- if neighbor == to_node:
99
- neighbors[i] = (to_node, weight)
143
+ if neighbor == to_vertex:
144
+ # Update existing edge weight
145
+ neighbors[i] = (to_vertex, weight)
100
146
  return
101
147
 
102
- neighbors.append((to_node, weight))
148
+ # Add new edge
149
+ neighbors.append((to_vertex, weight))
150
+ self._edge_count += 1
151
+
152
+ # For undirected graphs, add reverse edge
153
+ if not self._is_directed:
154
+ reverse_neighbors = self._adj_list[to_vertex]
155
+ # Check if reverse edge exists
156
+ found = False
157
+ for i, (neighbor, _) in enumerate(reverse_neighbors):
158
+ if neighbor == from_vertex:
159
+ reverse_neighbors[i] = (from_vertex, weight)
160
+ found = True
161
+ break
162
+ if not found:
163
+ reverse_neighbors.append((from_vertex, weight))
103
164
 
104
- def remove_edge(self, from_node: str, to_node: str) -> bool:
105
- """Remove an edge between two nodes."""
106
- if from_node not in self._adj_list:
165
+ def remove_edge(self, from_vertex: str, to_vertex: str) -> bool:
166
+ """
167
+ Remove an edge between two vertices.
168
+
169
+ Time: O(degree(from_vertex))
170
+
171
+ Returns:
172
+ True if edge was removed, False if not found
173
+ """
174
+ if from_vertex not in self._adj_list:
107
175
  return False
108
176
 
109
- neighbors = self._adj_list[from_node]
177
+ neighbors = self._adj_list[from_vertex]
110
178
  for i, (neighbor, _) in enumerate(neighbors):
111
- if neighbor == to_node:
179
+ if neighbor == to_vertex:
112
180
  neighbors.pop(i)
181
+ self._edge_count -= 1
182
+
183
+ # For undirected graphs, remove reverse edge
184
+ if not self._is_directed:
185
+ reverse_neighbors = self._adj_list.get(to_vertex, [])
186
+ for j, (n, _) in enumerate(reverse_neighbors):
187
+ if n == from_vertex:
188
+ reverse_neighbors.pop(j)
189
+ break
190
+
113
191
  return True
192
+
114
193
  return False
115
194
 
116
- def has_edge(self, from_node: str, to_node: str) -> bool:
117
- """Check if an edge exists between two nodes."""
118
- if from_node not in self._adj_list:
195
+ def remove_vertex(self, vertex: str) -> bool:
196
+ """
197
+ Remove a vertex and all its edges.
198
+
199
+ Time: O(V + E) worst case
200
+
201
+ Returns:
202
+ True if vertex was removed, False if not found
203
+ """
204
+ if vertex not in self._nodes:
119
205
  return False
120
206
 
121
- for neighbor, _ in self._adj_list[from_node]:
122
- if neighbor == to_node:
207
+ # Remove vertex data
208
+ del self._nodes[vertex]
209
+
210
+ # Count edges to be removed
211
+ edges_removed = len(self._adj_list.get(vertex, []))
212
+
213
+ # Remove all edges TO this vertex
214
+ for node in list(self._adj_list.keys()):
215
+ if node == vertex:
216
+ continue
217
+ self._adj_list[node] = [
218
+ (neighbor, weight)
219
+ for neighbor, weight in self._adj_list[node]
220
+ if neighbor != vertex
221
+ ]
222
+
223
+ # Remove vertex's adjacency list
224
+ if vertex in self._adj_list:
225
+ del self._adj_list[vertex]
226
+
227
+ self._edge_count -= edges_removed
228
+
229
+ return True
230
+
231
+ def get_neighbors(self, vertex: str) -> List[str]:
232
+ """
233
+ Get all neighbors of a vertex.
234
+
235
+ Time: O(1) to O(degree)
236
+
237
+ Returns:
238
+ List of neighbor vertices
239
+ """
240
+ if vertex not in self._adj_list:
241
+ return []
242
+ return [neighbor for neighbor, _ in self._adj_list[vertex]]
243
+
244
+ def get_neighbors_with_weights(self, vertex: str) -> List[Tuple[str, float]]:
245
+ """
246
+ Get all neighbors with edge weights.
247
+
248
+ Time: O(degree)
249
+
250
+ Returns:
251
+ List of (neighbor, weight) tuples
252
+ """
253
+ if vertex not in self._adj_list:
254
+ return []
255
+ return self._adj_list[vertex].copy()
256
+
257
+ def has_edge(self, from_vertex: str, to_vertex: str) -> bool:
258
+ """
259
+ Check if an edge exists.
260
+
261
+ Time: O(degree(from_vertex))
262
+ """
263
+ if from_vertex not in self._adj_list:
264
+ return False
265
+
266
+ for neighbor, _ in self._adj_list[from_vertex]:
267
+ if neighbor == to_vertex:
123
268
  return True
124
269
  return False
125
270
 
126
- def get_edge_weight(self, from_node: str, to_node: str) -> Optional[float]:
127
- """Get the weight of an edge between two nodes."""
128
- if from_node not in self._adj_list:
271
+ def get_edge_weight(self, from_vertex: str, to_vertex: str) -> Optional[float]:
272
+ """
273
+ Get the weight of an edge.
274
+
275
+ Time: O(degree(from_vertex))
276
+
277
+ Returns:
278
+ Edge weight or None if edge doesn't exist
279
+ """
280
+ if from_vertex not in self._adj_list:
129
281
  return None
130
282
 
131
- for neighbor, weight in self._adj_list[from_node]:
132
- if neighbor == to_node:
283
+ for neighbor, weight in self._adj_list[from_vertex]:
284
+ if neighbor == to_vertex:
133
285
  return weight
286
+
134
287
  return None
135
288
 
136
- def get_neighbors(self, node: str) -> List[str]:
137
- """Get all neighbors of a node."""
138
- if node not in self._adj_list:
139
- return []
140
- return [neighbor for neighbor, _ in self._adj_list[node]]
289
+ # ============================================================================
290
+ # GRAPH ANALYSIS METHODS
291
+ # ============================================================================
141
292
 
142
- def get_neighbors_with_weights(self, node: str) -> List[Tuple[str, float]]:
143
- """Get all neighbors with their edge weights."""
144
- if node not in self._adj_list:
145
- return []
146
- return self._adj_list[node].copy()
293
+ def degree(self, vertex: str) -> int:
294
+ """Get the degree of a vertex (out-degree for directed graphs)."""
295
+ if vertex not in self._adj_list:
296
+ return 0
297
+ return len(self._adj_list[vertex])
147
298
 
148
- def get_in_degree(self, node: str) -> int:
149
- """Get the in-degree of a node."""
299
+ def in_degree(self, vertex: str) -> int:
300
+ """
301
+ Get the in-degree of a vertex.
302
+
303
+ Time: O(E) - must scan all edges
304
+ """
305
+ if vertex not in self._nodes:
306
+ return 0
307
+
150
308
  count = 0
151
309
  for neighbors in self._adj_list.values():
152
310
  for neighbor, _ in neighbors:
153
- if neighbor == node:
311
+ if neighbor == vertex:
154
312
  count += 1
155
313
  return count
156
314
 
157
- def get_out_degree(self, node: str) -> int:
158
- """Get the out-degree of a node."""
159
- if node not in self._adj_list:
160
- return 0
161
- return len(self._adj_list[node])
315
+ def out_degree(self, vertex: str) -> int:
316
+ """Get the out-degree of a vertex (same as degree for directed graphs)."""
317
+ return self.degree(vertex)
162
318
 
163
- def get_degree(self, node: str) -> int:
164
- """Get the total degree of a node (in + out)."""
165
- return self.get_in_degree(node) + self.get_out_degree(node)
319
+ # ============================================================================
320
+ # GRAPH TRAVERSAL ALGORITHMS (Production-Grade)
321
+ # ============================================================================
166
322
 
167
- def is_connected(self, from_node: str, to_node: str) -> bool:
168
- """Check if two nodes are connected (BFS)."""
169
- if from_node not in self._nodes or to_node not in self._nodes:
170
- return False
323
+ def dfs(self, start: str, visit_fn: Optional[Callable[[str], None]] = None) -> List[str]:
324
+ """
325
+ Depth-First Search from start vertex.
326
+
327
+ Time: O(V + E)
328
+ Space: O(V) for recursion stack
171
329
 
172
- if from_node == to_node:
173
- return True
330
+ Args:
331
+ start: Starting vertex
332
+ visit_fn: Optional callback for each visited vertex
333
+
334
+ Returns:
335
+ List of visited vertices in DFS order
336
+ """
337
+ if start not in self._nodes:
338
+ return []
174
339
 
175
340
  visited = set()
176
- queue = [from_node]
341
+ result = []
177
342
 
178
- while queue:
179
- current = queue.pop(0)
180
- if current == to_node:
181
- return True
343
+ def dfs_recursive(vertex: str) -> None:
344
+ visited.add(vertex)
345
+ result.append(vertex)
346
+ if visit_fn:
347
+ visit_fn(vertex)
182
348
 
183
- if current in visited:
184
- continue
185
- visited.add(current)
349
+ for neighbor, _ in self._adj_list.get(vertex, []):
350
+ if neighbor not in visited:
351
+ dfs_recursive(neighbor)
352
+
353
+ dfs_recursive(start)
354
+ return result
355
+
356
+ def bfs(self, start: str, visit_fn: Optional[Callable[[str], None]] = None) -> List[str]:
357
+ """
358
+ Breadth-First Search from start vertex.
359
+
360
+ Time: O(V + E)
361
+ Space: O(V) for queue
362
+
363
+ Args:
364
+ start: Starting vertex
365
+ visit_fn: Optional callback for each visited vertex
366
+
367
+ Returns:
368
+ List of visited vertices in BFS order
369
+ """
370
+ if start not in self._nodes:
371
+ return []
372
+
373
+ visited = set([start])
374
+ result = [start]
375
+ queue = deque([start]) # Use deque for O(1) operations
376
+
377
+ if visit_fn:
378
+ visit_fn(start)
379
+
380
+ while queue:
381
+ vertex = queue.popleft() # O(1) with deque
186
382
 
187
- for neighbor, _ in self._adj_list.get(current, []):
383
+ for neighbor, _ in self._adj_list.get(vertex, []):
188
384
  if neighbor not in visited:
385
+ visited.add(neighbor)
386
+ result.append(neighbor)
189
387
  queue.append(neighbor)
388
+ if visit_fn:
389
+ visit_fn(neighbor)
190
390
 
191
- return False
391
+ return result
392
+
393
+ def find_path(self, start: str, end: str) -> List[str]:
394
+ """
395
+ Find path between two vertices using BFS.
396
+
397
+ Time: O(V + E)
398
+ Space: O(V)
399
+
400
+ Returns:
401
+ List representing path from start to end, or empty list if no path
402
+ """
403
+ if start not in self._nodes or end not in self._nodes:
404
+ return []
405
+
406
+ if start == end:
407
+ return [start]
408
+
409
+ visited = set([start])
410
+ queue = deque([(start, [start])])
411
+
412
+ while queue:
413
+ vertex, path = queue.popleft()
414
+
415
+ for neighbor, _ in self._adj_list.get(vertex, []):
416
+ if neighbor == end:
417
+ return path + [neighbor]
418
+
419
+ if neighbor not in visited:
420
+ visited.add(neighbor)
421
+ queue.append((neighbor, path + [neighbor]))
422
+
423
+ return [] # No path found
424
+
425
+ def has_cycle(self) -> bool:
426
+ """
427
+ Check if the graph contains a cycle.
428
+
429
+ Time: O(V + E)
430
+ Space: O(V)
431
+
432
+ Returns:
433
+ True if graph has a cycle, False otherwise
434
+ """
435
+ if not self._is_directed:
436
+ # Undirected graph cycle detection
437
+ visited = set()
438
+
439
+ def has_cycle_undirected(vertex: str, parent: Optional[str]) -> bool:
440
+ visited.add(vertex)
441
+
442
+ for neighbor, _ in self._adj_list.get(vertex, []):
443
+ if neighbor not in visited:
444
+ if has_cycle_undirected(neighbor, vertex):
445
+ return True
446
+ elif neighbor != parent:
447
+ return True
448
+
449
+ return False
450
+
451
+ for vertex in self._nodes:
452
+ if vertex not in visited:
453
+ if has_cycle_undirected(vertex, None):
454
+ return True
455
+
456
+ return False
457
+ else:
458
+ # Directed graph cycle detection (using DFS with colors)
459
+ WHITE, GRAY, BLACK = 0, 1, 2
460
+ color = {v: WHITE for v in self._nodes}
461
+
462
+ def has_cycle_directed(vertex: str) -> bool:
463
+ color[vertex] = GRAY
464
+
465
+ for neighbor, _ in self._adj_list.get(vertex, []):
466
+ if color[neighbor] == GRAY: # Back edge found
467
+ return True
468
+ if color[neighbor] == WHITE:
469
+ if has_cycle_directed(neighbor):
470
+ return True
471
+
472
+ color[vertex] = BLACK
473
+ return False
474
+
475
+ for vertex in self._nodes:
476
+ if color[vertex] == WHITE:
477
+ if has_cycle_directed(vertex):
478
+ return True
479
+
480
+ return False
481
+
482
+ def topological_sort(self) -> Optional[List[str]]:
483
+ """
484
+ Perform topological sort on the graph.
485
+
486
+ Time: O(V + E)
487
+ Space: O(V)
488
+
489
+ Returns:
490
+ List of vertices in topological order, or None if graph has cycle
491
+
492
+ Note: Only works on directed acyclic graphs (DAGs)
493
+ """
494
+ if not self._is_directed:
495
+ return None # Topological sort only for directed graphs
496
+
497
+ if self.has_cycle():
498
+ return None # Cannot topologically sort cyclic graphs
499
+
500
+ visited = set()
501
+ stack = []
502
+
503
+ def dfs_topological(vertex: str) -> None:
504
+ visited.add(vertex)
505
+
506
+ for neighbor, _ in self._adj_list.get(vertex, []):
507
+ if neighbor not in visited:
508
+ dfs_topological(neighbor)
509
+
510
+ stack.append(vertex)
511
+
512
+ for vertex in self._nodes:
513
+ if vertex not in visited:
514
+ dfs_topological(vertex)
515
+
516
+ return list(reversed(stack))
192
517
 
193
518
  def get_connected_components(self) -> List[Set[str]]:
194
- """Get all connected components in the graph."""
519
+ """
520
+ Get all connected components in the graph.
521
+
522
+ Time: O(V + E)
523
+ Space: O(V)
524
+
525
+ Returns:
526
+ List of sets, each containing vertices in a connected component
527
+ """
195
528
  visited = set()
196
529
  components = []
197
530
 
198
- for node in self._nodes:
199
- if node not in visited:
531
+ for start_vertex in self._nodes:
532
+ if start_vertex not in visited:
533
+ # BFS to find component
200
534
  component = set()
201
- queue = [node]
535
+ queue = deque([start_vertex])
536
+ visited.add(start_vertex)
202
537
 
203
538
  while queue:
204
- current = queue.pop(0)
205
- if current in visited:
206
- continue
207
-
208
- visited.add(current)
209
- component.add(current)
539
+ vertex = queue.popleft()
540
+ component.add(vertex)
210
541
 
211
- # Add neighbors
212
- for neighbor, _ in self._adj_list.get(current, []):
542
+ for neighbor, _ in self._adj_list.get(vertex, []):
213
543
  if neighbor not in visited:
544
+ visited.add(neighbor)
214
545
  queue.append(neighbor)
546
+
547
+ # For undirected graphs, also check incoming edges
548
+ if not self._is_directed:
549
+ for other_vertex in self._nodes:
550
+ if other_vertex not in visited:
551
+ for n, _ in self._adj_list.get(other_vertex, []):
552
+ if n == vertex:
553
+ visited.add(other_vertex)
554
+ queue.append(other_vertex)
555
+ break
215
556
 
216
- if component:
217
- components.append(component)
557
+ components.append(component)
218
558
 
219
559
  return components
220
560
 
561
+ def is_connected(self, from_vertex: str, to_vertex: str) -> bool:
562
+ """
563
+ Check if there's a path between two vertices.
564
+
565
+ Time: O(V + E)
566
+
567
+ Returns:
568
+ True if vertices are connected, False otherwise
569
+ """
570
+ return len(self.find_path(from_vertex, to_vertex)) > 0
571
+
572
+ # ============================================================================
573
+ # REQUIRED ABSTRACT METHODS (from ANodeStrategy)
574
+ # ============================================================================
575
+
576
+ def put(self, key: Any, value: Any = None) -> None:
577
+ """Store vertex with data."""
578
+ self.add_vertex(str(key), value)
579
+
580
+ def get(self, key: Any, default: Any = None) -> Any:
581
+ """Get vertex data."""
582
+ return self._nodes.get(str(key), default)
583
+
584
+ def has(self, key: Any) -> bool:
585
+ """Check if vertex exists."""
586
+ return str(key) in self._nodes
587
+
588
+ def delete(self, key: Any) -> bool:
589
+ """Delete a vertex and all its edges."""
590
+ return self.remove_vertex(str(key))
591
+
592
+ def keys(self) -> Iterator[Any]:
593
+ """Get all vertex IDs."""
594
+ return iter(self._nodes.keys())
595
+
596
+ def values(self) -> Iterator[Any]:
597
+ """Get all vertex data."""
598
+ return iter(self._nodes.values())
599
+
600
+ def items(self) -> Iterator[tuple[Any, Any]]:
601
+ """Get all vertices as (id, data) pairs."""
602
+ return iter(self._nodes.items())
603
+
604
+ # ============================================================================
605
+ # UTILITY METHODS
606
+ # ============================================================================
607
+
608
+ def size(self) -> int:
609
+ """Get the number of vertices."""
610
+ return len(self._nodes)
611
+
612
+ def is_empty(self) -> bool:
613
+ """Check if graph has no vertices."""
614
+ return len(self._nodes) == 0
615
+
221
616
  def clear(self) -> None:
222
- """Clear all nodes and edges."""
617
+ """Clear all vertices and edges."""
223
618
  self._nodes.clear()
224
619
  self._adj_list.clear()
620
+ self._edge_count = 0
225
621
 
226
- def __iter__(self) -> Iterator[str]:
227
- """Iterate through all nodes."""
228
- for node in self._nodes:
229
- yield node
622
+ def vertex_count(self) -> int:
623
+ """Get the number of vertices."""
624
+ return len(self._nodes)
230
625
 
231
- def __repr__(self) -> str:
232
- """String representation of the adjacency list."""
233
- return f"AdjacencyListStrategy({len(self._nodes)} nodes, {sum(len(neighbors) for neighbors in self._adj_list.values())} edges)"
626
+ def edge_count(self) -> int:
627
+ """Get the number of edges."""
628
+ return self._edge_count
234
629
 
235
- # Required abstract methods from base classes
236
- def find_path(self, start: Any, end: Any) -> List[Any]:
237
- """Find path between nodes using BFS."""
238
- if start not in self._nodes or end not in self._nodes:
239
- return []
240
-
241
- if start == end:
242
- return [start]
630
+ def vertices(self) -> List[str]:
631
+ """Get all vertices."""
632
+ return list(self._nodes.keys())
633
+
634
+ def edges(self) -> List[Tuple[str, str, float]]:
635
+ """Get all edges as (from, to, weight) tuples."""
636
+ result = []
637
+ for from_vertex, neighbors in self._adj_list.items():
638
+ for to_vertex, weight in neighbors:
639
+ result.append((from_vertex, to_vertex, weight))
640
+ return result
641
+
642
+ def to_native(self) -> Dict[str, Any]:
643
+ """Convert graph to native dictionary format."""
644
+ return {
645
+ 'vertices': self._nodes,
646
+ 'edges': {
647
+ vertex: [(neighbor, weight) for neighbor, weight in neighbors]
648
+ for vertex, neighbors in self._adj_list.items()
649
+ if neighbors
650
+ },
651
+ 'is_directed': self._is_directed,
652
+ 'vertex_count': self.vertex_count(),
653
+ 'edge_count': self.edge_count()
654
+ }
655
+
656
+ def from_native(self, data: Dict[str, Any]) -> None:
657
+ """Load graph from native dictionary format."""
658
+ self._nodes.clear()
659
+ self._adj_list.clear()
660
+ self._edge_count = 0
243
661
 
244
- visited = set()
245
- queue = [(start, [start])]
662
+ # Load vertices
663
+ for vertex, vertex_data in data.get('vertices', {}).items():
664
+ self._nodes[vertex] = vertex_data
246
665
 
247
- while queue:
248
- current, path = queue.pop(0)
249
- if current == end:
250
- return path
251
-
252
- if current in visited:
253
- continue
254
- visited.add(current)
255
-
256
- for neighbor, _ in self._adj_list.get(current, []):
257
- if neighbor not in visited:
258
- queue.append((neighbor, path + [neighbor]))
259
-
260
- return [] # No path found
666
+ # Load edges
667
+ self._is_directed = data.get('is_directed', True)
668
+ for vertex, neighbors in data.get('edges', {}).items():
669
+ for neighbor, weight in neighbors:
670
+ self.add_edge(vertex, neighbor, weight)
261
671
 
262
- def as_union_find(self):
263
- """Provide Union-Find behavioral view."""
264
- return self
265
672
 
266
- def as_neural_graph(self):
267
- """Provide Neural Graph behavioral view."""
268
- return self
673
+ # ============================================================================
674
+ # PYTHON SPECIAL METHODS
675
+ # ============================================================================
676
+
677
+ def __len__(self) -> int:
678
+ """Return the number of vertices."""
679
+ return len(self._nodes)
680
+
681
+ def __bool__(self) -> bool:
682
+ """Return True if graph has vertices."""
683
+ return bool(self._nodes)
684
+
685
+ def __contains__(self, vertex: str) -> bool:
686
+ """Check if vertex exists in graph."""
687
+ return vertex in self._nodes
688
+
689
+ def __iter__(self) -> Iterator[str]:
690
+ """Iterate through all vertices."""
691
+ return iter(self._nodes.keys())
269
692
 
270
- def as_flow_network(self):
271
- """Provide Flow Network behavioral view."""
272
- return self
693
+ def __repr__(self) -> str:
694
+ """Professional string representation."""
695
+ graph_type = "directed" if self._is_directed else "undirected"
696
+ return f"AdjacencyListStrategy(vertices={self.vertex_count()}, edges={self.edge_count()}, {graph_type})"
697
+
698
+ def __str__(self) -> str:
699
+ """Human-readable string representation."""
700
+ return f"Graph[V={self.vertex_count()}, E={self.edge_count()}]"
701
+
702
+ # ============================================================================
703
+ # PERFORMANCE METADATA
704
+ # ============================================================================
705
+
706
+ @property
707
+ def backend_info(self) -> Dict[str, Any]:
708
+ """Get backend implementation info."""
709
+ return {
710
+ 'strategy': 'ADJACENCY_LIST',
711
+ 'backend': 'dict of lists (defaultdict)',
712
+ 'graph_type': 'directed' if self._is_directed else 'undirected',
713
+ 'complexity': {
714
+ 'add_vertex': 'O(1)',
715
+ 'add_edge': 'O(1)',
716
+ 'remove_vertex': 'O(V + E)',
717
+ 'remove_edge': 'O(degree)',
718
+ 'get_neighbors': 'O(1)',
719
+ 'has_edge': 'O(degree)',
720
+ 'dfs': 'O(V + E)',
721
+ 'bfs': 'O(V + E)',
722
+ 'space': 'O(V + E)'
723
+ },
724
+ 'best_for': 'sparse graphs, dynamic graphs, neighbor queries',
725
+ 'thread_safe': False
726
+ }
727
+
728
+ @property
729
+ def metrics(self) -> Dict[str, Any]:
730
+ """Get performance metrics."""
731
+ avg_degree = self.edge_count() / self.vertex_count() if self.vertex_count() > 0 else 0
732
+ max_edges = self.vertex_count() * (self.vertex_count() - 1)
733
+ if not self._is_directed:
734
+ max_edges //= 2
735
+
736
+ sparsity = 1 - (self.edge_count() / max_edges) if max_edges > 0 else 1.0
737
+
738
+ return {
739
+ 'vertices': self.vertex_count(),
740
+ 'edges': self.edge_count(),
741
+ 'avg_degree': f"{avg_degree:.2f}",
742
+ 'sparsity': f"{sparsity:.2%}",
743
+ 'is_directed': self._is_directed,
744
+ 'memory_usage': f"{(self.vertex_count() * 8 + self.edge_count() * 16)} bytes (estimated)"
745
+ }