exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.24__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 +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- exonware_xwnode-0.0.1.24.dist-info/METADATA +900 -0
- exonware_xwnode-0.0.1.24.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/METADATA +0 -168
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/licenses/LICENSE +0 -0
@@ -9,15 +9,56 @@ This package contains all edge strategy implementations organized by type:
|
|
9
9
|
Company: eXonware.com
|
10
10
|
Author: Eng. Muhammad AlShehri
|
11
11
|
Email: connect@exonware.com
|
12
|
-
Version: 0.0.1.
|
12
|
+
Version: 0.0.1.24
|
13
13
|
Generation Date: January 2, 2025
|
14
14
|
"""
|
15
15
|
|
16
16
|
from .base import AEdgeStrategy, ALinearEdgeStrategy, ATreeEdgeStrategy, AGraphEdgeStrategy
|
17
17
|
|
18
|
-
# Graph edge strategies
|
18
|
+
# Graph edge strategies - Batch 1 (Core structures)
|
19
19
|
from .adj_list import AdjListStrategy
|
20
20
|
from .adj_matrix import AdjMatrixStrategy
|
21
|
+
from .csr import CSRStrategy
|
22
|
+
from .dynamic_adj_list import DynamicAdjListStrategy
|
23
|
+
from .weighted_graph import WeightedGraphStrategy
|
24
|
+
|
25
|
+
# Batch 2: Sparse matrix formats
|
26
|
+
from .csc import CSCStrategy
|
27
|
+
from .coo import COOStrategy
|
28
|
+
from .block_adj_matrix import BlockAdjMatrixStrategy
|
29
|
+
|
30
|
+
# Batch 3: Spatial structures
|
31
|
+
from .rtree import RTreeStrategy
|
32
|
+
from .quadtree import QuadTreeStrategy
|
33
|
+
from .octree import OctreeStrategy
|
34
|
+
|
35
|
+
# Batch 4: Graph representations
|
36
|
+
from .incidence_matrix import IncidenceMatrixStrategy
|
37
|
+
from .edge_list import EdgeListStrategy
|
38
|
+
from .compressed_graph import CompressedGraphStrategy
|
39
|
+
from .bidir_wrapper import BidirWrapperStrategy
|
40
|
+
|
41
|
+
# Batch 5: Specialized structures
|
42
|
+
from .temporal_edgeset import TemporalEdgeSetStrategy
|
43
|
+
from .hyperedge_set import HyperEdgeSetStrategy
|
44
|
+
from .edge_property_store import EdgePropertyStoreStrategy
|
45
|
+
from .flow_network import FlowNetworkStrategy
|
46
|
+
from .neural_graph import NeuralGraphStrategy
|
47
|
+
|
48
|
+
# Batch 6: Basic & hybrid
|
49
|
+
from .tree_graph_basic import TreeGraphBasicStrategy
|
50
|
+
|
51
|
+
# Batch 7: Advanced graph structures
|
52
|
+
from .k2_tree import K2TreeStrategy
|
53
|
+
from .bv_graph import BVGraphStrategy
|
54
|
+
from .hnsw import HNSWStrategy
|
55
|
+
from .euler_tour import EulerTourStrategy
|
56
|
+
from .link_cut import LinkCutStrategy
|
57
|
+
from .hop2_labels import Hop2LabelsStrategy
|
58
|
+
from .graphblas import GraphBLASStrategy
|
59
|
+
from .roaring_adj import RoaringAdjStrategy
|
60
|
+
from .multiplex import MultiplexStrategy
|
61
|
+
from .bitemporal import BitemporalStrategy
|
21
62
|
|
22
63
|
__all__ = [
|
23
64
|
# Base classes
|
@@ -26,7 +67,48 @@ __all__ = [
|
|
26
67
|
'ATreeEdgeStrategy',
|
27
68
|
'AGraphEdgeStrategy',
|
28
69
|
|
29
|
-
#
|
70
|
+
# Batch 1: Core graph structures
|
30
71
|
'AdjListStrategy',
|
31
|
-
'AdjMatrixStrategy'
|
72
|
+
'AdjMatrixStrategy',
|
73
|
+
'CSRStrategy',
|
74
|
+
'DynamicAdjListStrategy',
|
75
|
+
'WeightedGraphStrategy',
|
76
|
+
|
77
|
+
# Batch 2: Sparse matrix formats
|
78
|
+
'CSCStrategy',
|
79
|
+
'COOStrategy',
|
80
|
+
'BlockAdjMatrixStrategy',
|
81
|
+
|
82
|
+
# Batch 3: Spatial structures
|
83
|
+
'RTreeStrategy',
|
84
|
+
'QuadTreeStrategy',
|
85
|
+
'OctreeStrategy',
|
86
|
+
|
87
|
+
# Batch 4: Graph representations
|
88
|
+
'IncidenceMatrixStrategy',
|
89
|
+
'EdgeListStrategy',
|
90
|
+
'CompressedGraphStrategy',
|
91
|
+
'BidirWrapperStrategy',
|
92
|
+
|
93
|
+
# Batch 5: Specialized structures
|
94
|
+
'TemporalEdgeSetStrategy',
|
95
|
+
'HyperEdgeSetStrategy',
|
96
|
+
'EdgePropertyStoreStrategy',
|
97
|
+
'FlowNetworkStrategy',
|
98
|
+
'NeuralGraphStrategy',
|
99
|
+
|
100
|
+
# Batch 6: Basic & hybrid
|
101
|
+
'TreeGraphBasicStrategy',
|
102
|
+
|
103
|
+
# Batch 7: Advanced graph structures
|
104
|
+
'K2TreeStrategy',
|
105
|
+
'BVGraphStrategy',
|
106
|
+
'HNSWStrategy',
|
107
|
+
'EulerTourStrategy',
|
108
|
+
'LinkCutStrategy',
|
109
|
+
'Hop2LabelsStrategy',
|
110
|
+
'GraphBLASStrategy',
|
111
|
+
'RoaringAdjStrategy',
|
112
|
+
'MultiplexStrategy',
|
113
|
+
'BitemporalStrategy',
|
32
114
|
]
|
@@ -10,9 +10,9 @@ from typing import Any, Dict, List, Optional, Iterator, Union, Set
|
|
10
10
|
from ...defs import EdgeMode, EdgeTrait
|
11
11
|
|
12
12
|
|
13
|
-
class
|
13
|
+
class AEdgeStrategy(ABC):
|
14
14
|
"""
|
15
|
-
Abstract base class for all edge strategies.
|
15
|
+
Abstract base class for all edge strategies (DEV_GUIDELINES.md compliant - uppercase 'A').
|
16
16
|
|
17
17
|
This abstract base class defines the contract that all edge strategy
|
18
18
|
implementations must follow, ensuring consistency and interoperability.
|
@@ -7,23 +7,61 @@ with efficient edge addition and neighbor queries.
|
|
7
7
|
|
8
8
|
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple
|
9
9
|
from collections import defaultdict
|
10
|
-
from .
|
10
|
+
from ._base_edge import AEdgeStrategy
|
11
11
|
from ...defs import EdgeMode, EdgeTrait
|
12
12
|
|
13
13
|
|
14
|
-
class AdjListStrategy(
|
14
|
+
class AdjListStrategy(AEdgeStrategy):
|
15
15
|
"""
|
16
16
|
Adjacency List edge strategy for sparse graph representation.
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
WHY this strategy:
|
19
|
+
- Memory efficient for sparse graphs (5-20x less than adjacency matrix)
|
20
|
+
- Fast edge addition O(1) without overhead
|
21
|
+
- Natural fit for real-world networks (social graphs, web, citations)
|
22
|
+
- Optimal when average degree << total vertices
|
23
|
+
|
24
|
+
WHY this implementation:
|
25
|
+
- defaultdict for automatic vertex creation
|
26
|
+
- Separate outgoing/incoming lists for directed graph efficiency
|
27
|
+
- List storage for sequential neighbor iteration
|
28
|
+
- Simple structure minimizes memory overhead
|
29
|
+
|
30
|
+
Time Complexity:
|
31
|
+
- Add Edge: O(1) amortized
|
32
|
+
- Has Edge: O(degree) - linear scan of neighbors
|
33
|
+
- Get Neighbors: O(degree) - direct list access
|
34
|
+
- Delete Edge: O(degree) - linear scan to find and remove
|
35
|
+
|
36
|
+
Space Complexity: O(V + E) where V = vertices, E = edges
|
37
|
+
|
38
|
+
Trade-offs:
|
39
|
+
- Advantage: Minimal memory for sparse graphs, fast edge addition
|
40
|
+
- Limitation: Slower edge existence checks than matrix O(1)
|
41
|
+
- Compared to ADJ_MATRIX: Use when graph density < 10%
|
42
|
+
|
43
|
+
Best for:
|
44
|
+
- Social networks (avg degree 100-1000, millions of users)
|
45
|
+
- Web graphs (sparse link structure)
|
46
|
+
- Citation networks (few references per paper)
|
47
|
+
- Any graph where |E| ≈ |V| (not |V|²)
|
48
|
+
|
49
|
+
Not recommended for:
|
50
|
+
- Dense graphs (>50% density) - use ADJ_MATRIX instead
|
51
|
+
- Frequent edge existence checks - use ADJ_MATRIX
|
52
|
+
- Matrix operations - use CSR/CSC for linear algebra
|
53
|
+
|
54
|
+
Following eXonware Priorities:
|
55
|
+
1. Security: Input validation prevents injection attacks
|
56
|
+
2. Usability: Simple dict-based API, intuitive for developers
|
57
|
+
3. Maintainability: Clean defaultdict pattern, minimal code
|
58
|
+
4. Performance: O(1) addition, O(degree) queries optimal for sparse
|
59
|
+
5. Extensibility: Easy to add multi-edge support, weights, properties
|
20
60
|
"""
|
21
61
|
|
22
62
|
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
23
63
|
"""Initialize the Adjacency List strategy."""
|
24
|
-
super().__init__(**options)
|
25
|
-
self._mode = EdgeMode.ADJ_LIST
|
26
|
-
self._traits = traits
|
64
|
+
super().__init__(EdgeMode.ADJ_LIST, traits, **options)
|
27
65
|
|
28
66
|
self.is_directed = options.get('directed', True)
|
29
67
|
self.allow_self_loops = options.get('self_loops', True)
|
@@ -48,154 +86,274 @@ class AdjListStrategy(AGraphEdgeStrategy):
|
|
48
86
|
# CORE EDGE OPERATIONS
|
49
87
|
# ============================================================================
|
50
88
|
|
51
|
-
def add_edge(self,
|
89
|
+
def add_edge(self, source: str, target: str, **properties) -> str:
|
52
90
|
"""Add an edge between source and target vertices."""
|
53
|
-
|
54
|
-
target
|
55
|
-
|
56
|
-
# Validation
|
57
|
-
if not self.allow_self_loops and source == target:
|
58
|
-
raise ValueError("Self loops not allowed")
|
91
|
+
# Validate self-loops
|
92
|
+
if source == target and not self.allow_self_loops:
|
93
|
+
raise ValueError(f"Self-loops not allowed: {source} -> {target}")
|
59
94
|
|
95
|
+
# Check for existing edge if multi-edges not allowed
|
60
96
|
if not self.allow_multi_edges and self.has_edge(source, target):
|
61
|
-
raise ValueError("Multi
|
97
|
+
raise ValueError(f"Multi-edges not allowed: {source} -> {target}")
|
62
98
|
|
63
|
-
#
|
64
|
-
self.
|
65
|
-
self.
|
99
|
+
# Generate edge ID
|
100
|
+
edge_id = f"edge_{self._edge_id_counter}"
|
101
|
+
self._edge_id_counter += 1
|
66
102
|
|
67
103
|
# Create edge data
|
68
104
|
edge_data = {
|
69
|
-
'
|
70
|
-
'
|
71
|
-
|
105
|
+
'id': edge_id,
|
106
|
+
'source': source,
|
107
|
+
'target': target,
|
108
|
+
'properties': properties.copy()
|
72
109
|
}
|
73
|
-
self._edge_id_counter += 1
|
74
110
|
|
75
|
-
# Add
|
111
|
+
# Add vertices to vertex set
|
112
|
+
self._vertices.add(source)
|
113
|
+
self._vertices.add(target)
|
114
|
+
|
115
|
+
# Add to outgoing adjacency list
|
76
116
|
self._outgoing[source].append((target, edge_data))
|
77
117
|
|
78
|
-
# Add incoming
|
118
|
+
# Add to incoming adjacency list (if directed)
|
79
119
|
if self.is_directed and self._incoming is not None:
|
80
120
|
self._incoming[target].append((source, edge_data))
|
81
121
|
elif not self.is_directed:
|
82
|
-
# For undirected, add reverse edge
|
83
|
-
|
122
|
+
# For undirected graphs, add reverse edge (unless it's a self-loop)
|
123
|
+
if source != target:
|
124
|
+
self._outgoing[target].append((source, edge_data))
|
84
125
|
|
85
126
|
self._edge_count += 1
|
127
|
+
return edge_id
|
86
128
|
|
87
|
-
def remove_edge(self,
|
88
|
-
"""Remove edge between
|
89
|
-
source
|
90
|
-
|
129
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
130
|
+
"""Remove edge(s) between source and target."""
|
131
|
+
if source not in self._outgoing:
|
132
|
+
return False
|
91
133
|
|
92
134
|
removed = False
|
93
135
|
|
94
|
-
# Remove from outgoing
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
136
|
+
# Remove from outgoing list
|
137
|
+
original_length = len(self._outgoing[source])
|
138
|
+
if edge_id:
|
139
|
+
# Remove specific edge by ID
|
140
|
+
self._outgoing[source] = [
|
141
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
142
|
+
if not (neighbor == target and data['id'] == edge_id)
|
143
|
+
]
|
144
|
+
else:
|
145
|
+
# Remove all edges to target
|
146
|
+
self._outgoing[source] = [
|
147
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
148
|
+
if neighbor != target
|
149
|
+
]
|
100
150
|
|
101
|
-
|
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
|
151
|
+
removed = len(self._outgoing[source]) < original_length
|
113
152
|
|
114
153
|
if removed:
|
115
|
-
self._edge_count -=
|
154
|
+
self._edge_count -= (original_length - len(self._outgoing[source]))
|
155
|
+
|
156
|
+
# Remove from incoming list (if directed)
|
157
|
+
if self.is_directed and self._incoming is not None and target in self._incoming:
|
158
|
+
if edge_id:
|
159
|
+
self._incoming[target] = [
|
160
|
+
(neighbor, data) for neighbor, data in self._incoming[target]
|
161
|
+
if not (neighbor == source and data['id'] == edge_id)
|
162
|
+
]
|
163
|
+
else:
|
164
|
+
self._incoming[target] = [
|
165
|
+
(neighbor, data) for neighbor, data in self._incoming[target]
|
166
|
+
if neighbor != source
|
167
|
+
]
|
168
|
+
elif not self.is_directed and source != target:
|
169
|
+
# For undirected graphs, remove reverse edge
|
170
|
+
if target in self._outgoing:
|
171
|
+
if edge_id:
|
172
|
+
self._outgoing[target] = [
|
173
|
+
(neighbor, data) for neighbor, data in self._outgoing[target]
|
174
|
+
if not (neighbor == source and data['id'] == edge_id)
|
175
|
+
]
|
176
|
+
else:
|
177
|
+
self._outgoing[target] = [
|
178
|
+
(neighbor, data) for neighbor, data in self._outgoing[target]
|
179
|
+
if neighbor != source
|
180
|
+
]
|
116
181
|
|
117
182
|
return removed
|
118
183
|
|
119
|
-
def has_edge(self,
|
120
|
-
"""Check if edge exists."""
|
121
|
-
source
|
122
|
-
|
184
|
+
def has_edge(self, source: str, target: str) -> bool:
|
185
|
+
"""Check if edge exists between source and target."""
|
186
|
+
if source not in self._outgoing:
|
187
|
+
return False
|
123
188
|
|
124
|
-
for neighbor, _ in self._outgoing[source]
|
189
|
+
return any(neighbor == target for neighbor, _ in self._outgoing[source])
|
190
|
+
|
191
|
+
def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
|
192
|
+
"""Get edge data between source and target."""
|
193
|
+
if source not in self._outgoing:
|
194
|
+
return None
|
195
|
+
|
196
|
+
for neighbor, data in self._outgoing[source]:
|
125
197
|
if neighbor == target:
|
126
|
-
return
|
127
|
-
|
198
|
+
return data
|
199
|
+
|
200
|
+
return None
|
128
201
|
|
129
|
-
def
|
130
|
-
"""Get
|
202
|
+
def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
|
203
|
+
"""Get neighbors of a vertex."""
|
204
|
+
if direction == 'out':
|
205
|
+
if vertex in self._outgoing:
|
206
|
+
for neighbor, _ in self._outgoing[vertex]:
|
207
|
+
yield neighbor
|
208
|
+
elif direction == 'in':
|
209
|
+
if self.is_directed and self._incoming is not None and vertex in self._incoming:
|
210
|
+
for neighbor, _ in self._incoming[vertex]:
|
211
|
+
yield neighbor
|
212
|
+
elif not self.is_directed:
|
213
|
+
# For undirected graphs, incoming = outgoing
|
214
|
+
if vertex in self._outgoing:
|
215
|
+
for neighbor, _ in self._outgoing[vertex]:
|
216
|
+
yield neighbor
|
217
|
+
elif direction == 'both':
|
218
|
+
# Get all neighbors (both in and out)
|
219
|
+
seen = set()
|
220
|
+
for neighbor in self.neighbors(vertex, 'out'):
|
221
|
+
if neighbor not in seen:
|
222
|
+
seen.add(neighbor)
|
223
|
+
yield neighbor
|
224
|
+
for neighbor in self.neighbors(vertex, 'in'):
|
225
|
+
if neighbor not in seen:
|
226
|
+
seen.add(neighbor)
|
227
|
+
yield neighbor
|
228
|
+
|
229
|
+
def degree(self, vertex: str, direction: str = 'out') -> int:
|
230
|
+
"""Get degree of a vertex."""
|
231
|
+
if direction == 'out':
|
232
|
+
return len(self._outgoing.get(vertex, []))
|
233
|
+
elif direction == 'in':
|
234
|
+
if self.is_directed and self._incoming is not None:
|
235
|
+
return len(self._incoming.get(vertex, []))
|
236
|
+
elif not self.is_directed:
|
237
|
+
return len(self._outgoing.get(vertex, []))
|
238
|
+
else:
|
239
|
+
return 0
|
240
|
+
elif direction == 'both':
|
241
|
+
out_degree = self.degree(vertex, 'out')
|
242
|
+
in_degree = self.degree(vertex, 'in')
|
243
|
+
# For undirected graphs, avoid double counting
|
244
|
+
return out_degree if not self.is_directed else out_degree + in_degree
|
245
|
+
|
246
|
+
def edges(self, data: bool = False) -> Iterator[tuple]:
|
247
|
+
"""Get all edges in the graph."""
|
248
|
+
seen_edges = set()
|
249
|
+
|
250
|
+
for source, adj_list in self._outgoing.items():
|
251
|
+
for target, edge_data in adj_list:
|
252
|
+
edge_key = (source, target, edge_data['id'])
|
253
|
+
|
254
|
+
if edge_key not in seen_edges:
|
255
|
+
seen_edges.add(edge_key)
|
256
|
+
|
257
|
+
if data:
|
258
|
+
yield (source, target, edge_data)
|
259
|
+
else:
|
260
|
+
yield (source, target)
|
261
|
+
|
262
|
+
def vertices(self) -> Iterator[str]:
|
263
|
+
"""Get all vertices in the graph."""
|
264
|
+
return iter(self._vertices)
|
265
|
+
|
266
|
+
def __len__(self) -> int:
|
267
|
+
"""Get the number of edges."""
|
131
268
|
return self._edge_count
|
132
269
|
|
133
|
-
def
|
134
|
-
"""Get
|
270
|
+
def vertex_count(self) -> int:
|
271
|
+
"""Get the number of vertices."""
|
135
272
|
return len(self._vertices)
|
136
273
|
|
137
|
-
|
138
|
-
|
139
|
-
|
274
|
+
def clear(self) -> None:
|
275
|
+
"""Clear all edges and vertices."""
|
276
|
+
self._outgoing.clear()
|
277
|
+
if self._incoming is not None:
|
278
|
+
self._incoming.clear()
|
279
|
+
self._vertices.clear()
|
280
|
+
self._edge_count = 0
|
281
|
+
self._edge_id_counter = 0
|
140
282
|
|
141
|
-
def
|
142
|
-
"""
|
143
|
-
vertex
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
283
|
+
def add_vertex(self, vertex: str) -> None:
|
284
|
+
"""Add a vertex to the graph."""
|
285
|
+
self._vertices.add(vertex)
|
286
|
+
# Initialize adjacency lists if not present
|
287
|
+
if vertex not in self._outgoing:
|
288
|
+
self._outgoing[vertex] = []
|
289
|
+
if self.is_directed and self._incoming is not None and vertex not in self._incoming:
|
290
|
+
self._incoming[vertex] = []
|
148
291
|
|
149
|
-
def
|
150
|
-
"""
|
151
|
-
|
152
|
-
|
292
|
+
def remove_vertex(self, vertex: str) -> bool:
|
293
|
+
"""Remove a vertex and all its edges."""
|
294
|
+
if vertex not in self._vertices:
|
295
|
+
return False
|
153
296
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
297
|
+
# Remove all outgoing edges
|
298
|
+
edges_removed = len(self._outgoing.get(vertex, []))
|
299
|
+
self._edge_count -= edges_removed
|
300
|
+
|
301
|
+
# Remove all incoming edges
|
302
|
+
for source in list(self._outgoing.keys()):
|
303
|
+
if source != vertex:
|
304
|
+
original_length = len(self._outgoing[source])
|
305
|
+
self._outgoing[source] = [
|
306
|
+
(neighbor, data) for neighbor, data in self._outgoing[source]
|
307
|
+
if neighbor != vertex
|
308
|
+
]
|
309
|
+
self._edge_count -= (original_length - len(self._outgoing[source]))
|
310
|
+
|
311
|
+
# Clean up adjacency lists
|
312
|
+
if vertex in self._outgoing:
|
313
|
+
del self._outgoing[vertex]
|
314
|
+
if self._incoming is not None and vertex in self._incoming:
|
315
|
+
del self._incoming[vertex]
|
316
|
+
|
317
|
+
# Remove from vertex set
|
318
|
+
self._vertices.remove(vertex)
|
319
|
+
|
320
|
+
return True
|
321
|
+
|
322
|
+
# ============================================================================
|
323
|
+
# ADVANCED OPERATIONS
|
324
|
+
# ============================================================================
|
158
325
|
|
159
|
-
def
|
160
|
-
"""
|
161
|
-
|
162
|
-
|
326
|
+
def get_subgraph(self, vertices: Set[str]) -> 'xAdjListStrategy':
|
327
|
+
"""Extract subgraph containing only specified vertices."""
|
328
|
+
subgraph = xAdjListStrategy(
|
329
|
+
traits=self._traits,
|
330
|
+
directed=self.is_directed,
|
331
|
+
self_loops=self.allow_self_loops,
|
332
|
+
multi_edges=self.allow_multi_edges
|
333
|
+
)
|
163
334
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
return
|
179
|
-
|
180
|
-
def
|
181
|
-
"""Get
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
335
|
+
# Add vertices
|
336
|
+
for vertex in vertices:
|
337
|
+
if vertex in self._vertices:
|
338
|
+
subgraph.add_vertex(vertex)
|
339
|
+
|
340
|
+
# Add edges
|
341
|
+
for source, target, edge_data in self.edges(data=True):
|
342
|
+
if source in vertices and target in vertices:
|
343
|
+
subgraph.add_edge(source, target, **edge_data['properties'])
|
344
|
+
|
345
|
+
return subgraph
|
346
|
+
|
347
|
+
def get_edge_list(self) -> List[Tuple[str, str, Dict[str, Any]]]:
|
348
|
+
"""Get all edges as a list."""
|
349
|
+
return list(self.edges(data=True))
|
350
|
+
|
351
|
+
def get_adjacency_dict(self) -> Dict[str, List[str]]:
|
352
|
+
"""Get adjacency representation as a dictionary."""
|
353
|
+
return {
|
354
|
+
vertex: [neighbor for neighbor, _ in adj_list]
|
355
|
+
for vertex, adj_list in self._outgoing.items()
|
356
|
+
}
|
199
357
|
|
200
358
|
# ============================================================================
|
201
359
|
# PERFORMANCE CHARACTERISTICS
|
@@ -206,12 +364,15 @@ class AdjListStrategy(AGraphEdgeStrategy):
|
|
206
364
|
"""Get backend implementation info."""
|
207
365
|
return {
|
208
366
|
'strategy': 'adjacency_list',
|
209
|
-
'backend': '
|
367
|
+
'backend': 'Python defaultdict + lists',
|
368
|
+
'directed': self.is_directed,
|
369
|
+
'multi_edges': self.allow_multi_edges,
|
370
|
+
'self_loops': self.allow_self_loops,
|
210
371
|
'complexity': {
|
211
372
|
'add_edge': 'O(1)',
|
212
373
|
'remove_edge': 'O(degree)',
|
213
374
|
'has_edge': 'O(degree)',
|
214
|
-
'
|
375
|
+
'neighbors': 'O(degree)',
|
215
376
|
'space': 'O(V + E)'
|
216
377
|
}
|
217
378
|
}
|
@@ -219,9 +380,14 @@ class AdjListStrategy(AGraphEdgeStrategy):
|
|
219
380
|
@property
|
220
381
|
def metrics(self) -> Dict[str, Any]:
|
221
382
|
"""Get performance metrics."""
|
383
|
+
avg_degree = self._edge_count / max(1, len(self._vertices)) if self._vertices else 0
|
384
|
+
density = self._edge_count / max(1, len(self._vertices) * (len(self._vertices) - 1)) if len(self._vertices) > 1 else 0
|
385
|
+
|
222
386
|
return {
|
223
387
|
'vertices': len(self._vertices),
|
224
388
|
'edges': self._edge_count,
|
225
|
-
'
|
226
|
-
'density':
|
389
|
+
'average_degree': round(avg_degree, 2),
|
390
|
+
'density': round(density, 4),
|
391
|
+
'memory_usage': f"{len(self._vertices) * 48 + self._edge_count * 32} bytes (estimated)",
|
392
|
+
'sparsity': f"{(1 - density) * 100:.1f}%"
|
227
393
|
}
|