exonware-xwnode 0.0.1.21__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 (250) hide show
  1. exonware/__init__.py +8 -1
  2. exonware/xwnode/__init__.py +18 -5
  3. exonware/xwnode/add_strategy_types.py +165 -0
  4. exonware/xwnode/base.py +7 -5
  5. exonware/xwnode/common/__init__.py +1 -1
  6. exonware/xwnode/common/graph/__init__.py +30 -0
  7. exonware/xwnode/common/graph/caching.py +131 -0
  8. exonware/xwnode/common/graph/contracts.py +100 -0
  9. exonware/xwnode/common/graph/errors.py +44 -0
  10. exonware/xwnode/common/graph/indexing.py +260 -0
  11. exonware/xwnode/common/graph/manager.py +568 -0
  12. exonware/xwnode/common/management/__init__.py +3 -5
  13. exonware/xwnode/common/management/manager.py +9 -9
  14. exonware/xwnode/common/management/migration.py +6 -6
  15. exonware/xwnode/common/monitoring/__init__.py +3 -5
  16. exonware/xwnode/common/monitoring/metrics.py +7 -3
  17. exonware/xwnode/common/monitoring/pattern_detector.py +2 -2
  18. exonware/xwnode/common/monitoring/performance_monitor.py +6 -2
  19. exonware/xwnode/common/patterns/__init__.py +3 -5
  20. exonware/xwnode/common/patterns/advisor.py +1 -1
  21. exonware/xwnode/common/patterns/flyweight.py +6 -2
  22. exonware/xwnode/common/patterns/registry.py +203 -184
  23. exonware/xwnode/common/utils/__init__.py +25 -11
  24. exonware/xwnode/common/utils/simple.py +1 -1
  25. exonware/xwnode/config.py +3 -8
  26. exonware/xwnode/contracts.py +4 -105
  27. exonware/xwnode/defs.py +413 -159
  28. exonware/xwnode/edges/strategies/__init__.py +86 -4
  29. exonware/xwnode/edges/strategies/_base_edge.py +2 -2
  30. exonware/xwnode/edges/strategies/adj_list.py +287 -121
  31. exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
  32. exonware/xwnode/edges/strategies/base.py +1 -1
  33. exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
  34. exonware/xwnode/edges/strategies/bitemporal.py +520 -0
  35. exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
  36. exonware/xwnode/edges/strategies/bv_graph.py +664 -0
  37. exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
  38. exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
  39. exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
  40. exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
  41. exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
  42. exonware/xwnode/edges/strategies/edge_list.py +168 -0
  43. exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
  44. exonware/xwnode/edges/strategies/euler_tour.py +560 -0
  45. exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
  46. exonware/xwnode/edges/strategies/graphblas.py +449 -0
  47. exonware/xwnode/edges/strategies/hnsw.py +637 -0
  48. exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
  49. exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
  50. exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
  51. exonware/xwnode/edges/strategies/k2_tree.py +613 -0
  52. exonware/xwnode/edges/strategies/link_cut.py +626 -0
  53. exonware/xwnode/edges/strategies/multiplex.py +532 -0
  54. exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
  55. exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
  56. exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
  57. exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
  58. exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
  59. exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
  60. exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
  61. exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
  62. exonware/xwnode/errors.py +3 -6
  63. exonware/xwnode/facade.py +20 -20
  64. exonware/xwnode/nodes/strategies/__init__.py +29 -9
  65. exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
  66. exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
  67. exonware/xwnode/nodes/strategies/array_list.py +36 -3
  68. exonware/xwnode/nodes/strategies/art.py +581 -0
  69. exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
  70. exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
  71. exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
  72. exonware/xwnode/nodes/strategies/base.py +469 -98
  73. exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
  74. exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
  75. exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
  76. exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
  77. exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
  78. exonware/xwnode/nodes/strategies/contracts.py +1 -1
  79. exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
  80. exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
  81. exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
  82. exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
  83. exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
  84. exonware/xwnode/nodes/strategies/dawg.py +876 -0
  85. exonware/xwnode/nodes/strategies/deque.py +321 -153
  86. exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
  87. exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
  88. exonware/xwnode/nodes/strategies/hamt.py +403 -0
  89. exonware/xwnode/nodes/strategies/hash_map.py +354 -67
  90. exonware/xwnode/nodes/strategies/heap.py +105 -5
  91. exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
  92. exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
  93. exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
  94. exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
  95. exonware/xwnode/nodes/strategies/learned_index.py +533 -0
  96. exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
  97. exonware/xwnode/nodes/strategies/linked_list.py +316 -119
  98. exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
  99. exonware/xwnode/nodes/strategies/masstree.py +130 -0
  100. exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
  101. exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
  102. exonware/xwnode/nodes/strategies/queue.py +249 -120
  103. exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
  104. exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
  105. exonware/xwnode/nodes/strategies/rope.py +717 -0
  106. exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
  107. exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
  108. exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
  109. exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
  110. exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
  111. exonware/xwnode/nodes/strategies/stack.py +244 -112
  112. exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
  113. exonware/xwnode/nodes/strategies/t_tree.py +94 -0
  114. exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
  115. exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
  116. exonware/xwnode/nodes/strategies/trie.py +153 -9
  117. exonware/xwnode/nodes/strategies/union_find.py +111 -5
  118. exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
  119. exonware/xwnode/strategies/__init__.py +5 -51
  120. exonware/xwnode/version.py +3 -3
  121. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
  122. exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
  123. exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
  124. exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
  125. exonware/xwnode/nodes/strategies/_base_node.py +0 -307
  126. exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
  127. exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
  128. exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
  129. exonware/xwnode/nodes/strategies/node_heap.py +0 -196
  130. exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
  131. exonware/xwnode/nodes/strategies/node_trie.py +0 -257
  132. exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
  133. exonware/xwnode/queries/executors/__init__.py +0 -47
  134. exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
  135. exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
  136. exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
  137. exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
  138. exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
  139. exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
  140. exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
  141. exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
  142. exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
  143. exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
  144. exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
  145. exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
  146. exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
  147. exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
  148. exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
  149. exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
  150. exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
  151. exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
  152. exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
  153. exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
  154. exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
  155. exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
  156. exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
  157. exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
  158. exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
  159. exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
  160. exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
  161. exonware/xwnode/queries/executors/array/__init__.py +0 -9
  162. exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
  163. exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
  164. exonware/xwnode/queries/executors/base.py +0 -257
  165. exonware/xwnode/queries/executors/capability_checker.py +0 -204
  166. exonware/xwnode/queries/executors/contracts.py +0 -166
  167. exonware/xwnode/queries/executors/core/__init__.py +0 -17
  168. exonware/xwnode/queries/executors/core/create_executor.py +0 -96
  169. exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
  170. exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
  171. exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
  172. exonware/xwnode/queries/executors/core/select_executor.py +0 -152
  173. exonware/xwnode/queries/executors/core/update_executor.py +0 -102
  174. exonware/xwnode/queries/executors/data/__init__.py +0 -13
  175. exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
  176. exonware/xwnode/queries/executors/data/load_executor.py +0 -50
  177. exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
  178. exonware/xwnode/queries/executors/data/store_executor.py +0 -50
  179. exonware/xwnode/queries/executors/defs.py +0 -93
  180. exonware/xwnode/queries/executors/engine.py +0 -221
  181. exonware/xwnode/queries/executors/errors.py +0 -68
  182. exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
  183. exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
  184. exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
  185. exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
  186. exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
  187. exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
  188. exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
  189. exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
  190. exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
  191. exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
  192. exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
  193. exonware/xwnode/queries/executors/graph/__init__.py +0 -15
  194. exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
  195. exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
  196. exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
  197. exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
  198. exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
  199. exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
  200. exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
  201. exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
  202. exonware/xwnode/queries/executors/projection/__init__.py +0 -9
  203. exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
  204. exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
  205. exonware/xwnode/queries/executors/registry.py +0 -173
  206. exonware/xwnode/queries/parsers/__init__.py +0 -26
  207. exonware/xwnode/queries/parsers/base.py +0 -86
  208. exonware/xwnode/queries/parsers/contracts.py +0 -46
  209. exonware/xwnode/queries/parsers/errors.py +0 -53
  210. exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
  211. exonware/xwnode/queries/strategies/__init__.py +0 -24
  212. exonware/xwnode/queries/strategies/base.py +0 -236
  213. exonware/xwnode/queries/strategies/cql.py +0 -201
  214. exonware/xwnode/queries/strategies/cypher.py +0 -181
  215. exonware/xwnode/queries/strategies/datalog.py +0 -70
  216. exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
  217. exonware/xwnode/queries/strategies/eql.py +0 -70
  218. exonware/xwnode/queries/strategies/flux.py +0 -70
  219. exonware/xwnode/queries/strategies/gql.py +0 -70
  220. exonware/xwnode/queries/strategies/graphql.py +0 -240
  221. exonware/xwnode/queries/strategies/gremlin.py +0 -181
  222. exonware/xwnode/queries/strategies/hiveql.py +0 -214
  223. exonware/xwnode/queries/strategies/hql.py +0 -70
  224. exonware/xwnode/queries/strategies/jmespath.py +0 -219
  225. exonware/xwnode/queries/strategies/jq.py +0 -66
  226. exonware/xwnode/queries/strategies/json_query.py +0 -66
  227. exonware/xwnode/queries/strategies/jsoniq.py +0 -248
  228. exonware/xwnode/queries/strategies/kql.py +0 -70
  229. exonware/xwnode/queries/strategies/linq.py +0 -238
  230. exonware/xwnode/queries/strategies/logql.py +0 -70
  231. exonware/xwnode/queries/strategies/mql.py +0 -68
  232. exonware/xwnode/queries/strategies/n1ql.py +0 -210
  233. exonware/xwnode/queries/strategies/partiql.py +0 -70
  234. exonware/xwnode/queries/strategies/pig.py +0 -215
  235. exonware/xwnode/queries/strategies/promql.py +0 -70
  236. exonware/xwnode/queries/strategies/sparql.py +0 -220
  237. exonware/xwnode/queries/strategies/sql.py +0 -275
  238. exonware/xwnode/queries/strategies/xml_query.py +0 -66
  239. exonware/xwnode/queries/strategies/xpath.py +0 -223
  240. exonware/xwnode/queries/strategies/xquery.py +0 -258
  241. exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
  242. exonware/xwnode/queries/strategies/xwquery.py +0 -456
  243. exonware_xwnode-0.0.1.21.dist-info/RECORD +0 -214
  244. /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
  245. /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
  246. /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
  247. /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
  248. /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
  249. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
  250. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,626 @@
1
+ """
2
+ #exonware/xwnode/src/exonware/xwnode/edges/strategies/link_cut.py
3
+
4
+ Link-Cut Trees Edge Strategy Implementation
5
+
6
+ This module implements the LINK_CUT strategy for dynamic trees with
7
+ path queries and updates using splay-based structure.
8
+
9
+ Company: eXonware.com
10
+ Author: Eng. Muhammad AlShehri
11
+ Email: connect@exonware.com
12
+ Version: 0.0.1.23
13
+ Generation Date: 12-Oct-2025
14
+ """
15
+
16
+ from typing import Any, Iterator, Dict, List, Set, Optional, Tuple
17
+ from collections import deque, defaultdict
18
+ from ._base_edge import AEdgeStrategy
19
+ from ...defs import EdgeMode, EdgeTrait
20
+ from ...errors import XWNodeError, XWNodeValueError
21
+
22
+
23
+ class LCNode:
24
+ """
25
+ Node in link-cut tree.
26
+
27
+ WHY splay tree representation:
28
+ - Preferred paths stored in splay trees
29
+ - Access/expose operations bring nodes to root
30
+ - Amortized O(log n) complexity
31
+ """
32
+
33
+ def __init__(self, vertex: str):
34
+ """
35
+ Initialize link-cut node.
36
+
37
+ Args:
38
+ vertex: Vertex identifier
39
+ """
40
+ self.vertex = vertex
41
+ self.parent: Optional['LCNode'] = None
42
+ self.left: Optional['LCNode'] = None
43
+ self.right: Optional['LCNode'] = None
44
+
45
+ # Path aggregate values (for path queries)
46
+ self.value: float = 0.0
47
+ self.path_min: float = 0.0
48
+ self.path_max: float = 0.0
49
+ self.path_sum: float = 0.0
50
+
51
+ # Lazy propagation flag
52
+ self.reversed = False
53
+
54
+ def is_root(self) -> bool:
55
+ """Check if this is a root of preferred path."""
56
+ return self.parent is None or (
57
+ self.parent.left != self and self.parent.right != self
58
+ )
59
+
60
+ def push_down(self) -> None:
61
+ """Push down lazy reverse flag."""
62
+ if self.reversed:
63
+ self.left, self.right = self.right, self.left
64
+
65
+ if self.left:
66
+ self.left.reversed = not self.left.reversed
67
+ if self.right:
68
+ self.right.reversed = not self.right.reversed
69
+
70
+ self.reversed = False
71
+
72
+ def update(self) -> None:
73
+ """Update path aggregate values from children."""
74
+ self.path_min = self.value
75
+ self.path_max = self.value
76
+ self.path_sum = self.value
77
+
78
+ if self.left:
79
+ self.path_min = min(self.path_min, self.left.path_min)
80
+ self.path_max = max(self.path_max, self.left.path_max)
81
+ self.path_sum += self.left.path_sum
82
+
83
+ if self.right:
84
+ self.path_min = min(self.path_min, self.right.path_min)
85
+ self.path_max = max(self.path_max, self.right.path_max)
86
+ self.path_sum += self.right.path_sum
87
+
88
+
89
+ class LinkCutStrategy(AEdgeStrategy):
90
+ """
91
+ Link-Cut Trees strategy for dynamic trees with path operations.
92
+
93
+ WHY Link-Cut Trees:
94
+ - More powerful than Euler Tour Trees (supports path queries)
95
+ - O(log n) amortized for link, cut, and path operations
96
+ - Enables dynamic minimum spanning tree
97
+ - Supports path aggregates (sum, min, max) efficiently
98
+ - Essential for network flow and matching algorithms
99
+
100
+ WHY this implementation:
101
+ - Splay trees for preferred path representation
102
+ - Access operation brings nodes to root
103
+ - Lazy reversal for efficient path direction changes
104
+ - Path aggregates maintained incrementally
105
+ - Simplified exposure operation for clarity
106
+
107
+ Time Complexity (all amortized):
108
+ - Link: O(log n)
109
+ - Cut: O(log n)
110
+ - Connected: O(log n)
111
+ - Find root: O(log n)
112
+ - Path aggregate (sum/min/max): O(log n)
113
+ - Make root: O(log n)
114
+
115
+ Space Complexity: O(n) for n vertices
116
+
117
+ Trade-offs:
118
+ - Advantage: Path queries in addition to connectivity
119
+ - Advantage: Fully dynamic (link/cut)
120
+ - Advantage: Amortized O(log n) guarantees
121
+ - Limitation: More complex than Euler Tour Trees
122
+ - Limitation: Amortized (not worst-case) bounds
123
+ - Limitation: Splay tree implementation complexity
124
+ - Compared to Euler Tour: More features, more complex
125
+ - Compared to Heavy-Light: Simpler, similar performance
126
+
127
+ Best for:
128
+ - Dynamic minimum spanning trees
129
+ - Network flow algorithms
130
+ - Dynamic graph matching
131
+ - Path aggregate queries on trees
132
+ - Root changes in dynamic trees
133
+ - Bipartite matching algorithms
134
+
135
+ Not recommended for:
136
+ - Static trees (use LCA preprocessing)
137
+ - Simple connectivity (use Union-Find or Euler Tour)
138
+ - When path queries not needed
139
+ - Directed acyclic graphs
140
+ - Fixed root scenarios
141
+
142
+ Following eXonware Priorities:
143
+ 1. Security: Validates tree structure, prevents cycles
144
+ 2. Usability: Clean link/cut/path_query API
145
+ 3. Maintainability: Modular splay operations
146
+ 4. Performance: O(log n) amortized operations
147
+ 5. Extensibility: Easy to add more aggregates, lazy propagation
148
+
149
+ Industry Best Practices:
150
+ - Follows Sleator-Tarjan link-cut trees (1983)
151
+ - Uses splay trees for preferred paths
152
+ - Implements access/expose operations correctly
153
+ - Provides path aggregate queries
154
+ - Compatible with dynamic graph algorithms
155
+ """
156
+
157
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
158
+ """
159
+ Initialize link-cut tree strategy.
160
+
161
+ Args:
162
+ traits: Edge traits
163
+ **options: Additional options
164
+ """
165
+ super().__init__(EdgeMode.LINK_CUT, traits, **options)
166
+
167
+ # Node storage
168
+ self._nodes: Dict[str, LCNode] = {}
169
+
170
+ # Edge tracking
171
+ self._edges: Set[Tuple[str, str]] = set()
172
+
173
+ # Vertices
174
+ self._vertices: Set[str] = set()
175
+
176
+ def get_supported_traits(self) -> EdgeTrait:
177
+ """Get supported traits."""
178
+ return EdgeTrait.DIRECTED | EdgeTrait.SPARSE
179
+
180
+ # ============================================================================
181
+ # SPLAY OPERATIONS
182
+ # ============================================================================
183
+
184
+ def _rotate(self, node: LCNode) -> None:
185
+ """Rotate node with parent."""
186
+ parent = node.parent
187
+ if parent is None:
188
+ return
189
+
190
+ grandparent = parent.parent
191
+
192
+ if parent.left == node:
193
+ # Right rotation
194
+ parent.left = node.right
195
+ if node.right:
196
+ node.right.parent = parent
197
+ node.right = parent
198
+ else:
199
+ # Left rotation
200
+ parent.right = node.left
201
+ if node.left:
202
+ node.left.parent = parent
203
+ node.left = parent
204
+
205
+ parent.parent = node
206
+ node.parent = grandparent
207
+
208
+ if grandparent:
209
+ if grandparent.left == parent:
210
+ grandparent.left = node
211
+ elif grandparent.right == parent:
212
+ grandparent.right = node
213
+
214
+ # Update aggregates
215
+ parent.update()
216
+ node.update()
217
+
218
+ def _splay(self, node: LCNode) -> None:
219
+ """
220
+ Splay node to root of its tree.
221
+
222
+ Args:
223
+ node: Node to splay
224
+
225
+ WHY splaying:
226
+ - Brings frequently accessed nodes closer to root
227
+ - Amortizes access cost
228
+ - Critical for O(log n) amortized complexity
229
+ """
230
+ while not node.is_root():
231
+ parent = node.parent
232
+
233
+ if parent.is_root():
234
+ # Zig step
235
+ self._rotate(node)
236
+ else:
237
+ grandparent = parent.parent
238
+
239
+ if (grandparent.left == parent) == (parent.left == node):
240
+ # Zig-zig
241
+ self._rotate(parent)
242
+ self._rotate(node)
243
+ else:
244
+ # Zig-zag
245
+ self._rotate(node)
246
+ self._rotate(node)
247
+
248
+ def _access(self, node: LCNode) -> None:
249
+ """
250
+ Make path from node to root preferred.
251
+
252
+ Args:
253
+ node: Node to access
254
+
255
+ WHY access:
256
+ - Makes path to root represented by single splay tree
257
+ - Enables path queries
258
+ - Essential operation for link-cut trees
259
+ """
260
+ self._splay(node)
261
+ if node.right:
262
+ node.right.parent = None
263
+ node.right = None
264
+ node.update()
265
+
266
+ while node.parent:
267
+ parent = node.parent
268
+ self._splay(parent)
269
+ if parent.right:
270
+ parent.right.parent = None
271
+ parent.right = node
272
+ parent.update()
273
+ self._splay(node)
274
+
275
+ # ============================================================================
276
+ # LINK-CUT OPERATIONS
277
+ # ============================================================================
278
+
279
+ def _get_or_create_node(self, vertex: str) -> LCNode:
280
+ """Get or create LC node for vertex."""
281
+ if vertex not in self._nodes:
282
+ self._nodes[vertex] = LCNode(vertex)
283
+ self._vertices.add(vertex)
284
+ return self._nodes[vertex]
285
+
286
+ def _link(self, u: str, v: str) -> None:
287
+ """
288
+ Link vertices u and v.
289
+
290
+ Args:
291
+ u: First vertex
292
+ v: Second vertex (becomes parent of u)
293
+
294
+ Raises:
295
+ XWNodeError: If would create cycle
296
+ """
297
+ node_u = self._get_or_create_node(u)
298
+ node_v = self._get_or_create_node(v)
299
+
300
+ # Check if already connected
301
+ if self._find_root(node_u) == self._find_root(node_v):
302
+ raise XWNodeError(f"Link would create cycle: {u} and {v} already connected")
303
+
304
+ # Make u child of v
305
+ self._access(node_u)
306
+ self._access(node_v)
307
+ node_u.parent = node_v
308
+
309
+ def _cut(self, u: str, v: str) -> bool:
310
+ """
311
+ Cut edge between u and v.
312
+
313
+ Args:
314
+ u: First vertex
315
+ v: Second vertex
316
+
317
+ Returns:
318
+ True if cut successful
319
+ """
320
+ if u not in self._nodes or v not in self._nodes:
321
+ return False
322
+
323
+ node_u = self._nodes[u]
324
+ node_v = self._nodes[v]
325
+
326
+ # Make path from u to v preferred
327
+ self._access(node_u)
328
+ self._access(node_v)
329
+
330
+ # Check if v is parent of u
331
+ if node_u.parent == node_v:
332
+ node_u.parent = None
333
+ return True
334
+
335
+ # Check if u is parent of v
336
+ if node_v.parent == node_u:
337
+ node_v.parent = None
338
+ return True
339
+
340
+ return False
341
+
342
+ def _find_root(self, node: LCNode) -> LCNode:
343
+ """
344
+ Find root of tree containing node.
345
+
346
+ Args:
347
+ node: Node
348
+
349
+ Returns:
350
+ Root node
351
+ """
352
+ self._access(node)
353
+
354
+ # Move to leftmost node
355
+ while node.left:
356
+ node = node.left
357
+
358
+ self._splay(node)
359
+ return node
360
+
361
+ # ============================================================================
362
+ # PATH QUERIES
363
+ # ============================================================================
364
+
365
+ def path_aggregate(self, u: str, v: str, operation: str = "sum") -> float:
366
+ """
367
+ Compute aggregate on path from u to v.
368
+
369
+ Args:
370
+ u: Start vertex
371
+ v: End vertex
372
+ operation: Aggregate operation (sum, min, max)
373
+
374
+ Returns:
375
+ Aggregate value
376
+
377
+ Raises:
378
+ XWNodeValueError: If vertices not in same tree
379
+
380
+ WHY path aggregates:
381
+ - Essential for network flow
382
+ - Enables dynamic programming on trees
383
+ - Maintained incrementally in O(log n)
384
+ """
385
+ if u not in self._nodes or v not in self._nodes:
386
+ raise XWNodeValueError(f"Vertices not found: {u}, {v}")
387
+
388
+ node_u = self._nodes[u]
389
+ node_v = self._nodes[v]
390
+
391
+ # Check if in same tree
392
+ if self._find_root(node_u) != self._find_root(node_v):
393
+ raise XWNodeValueError(f"Vertices {u} and {v} not in same tree")
394
+
395
+ # Access path from u to v
396
+ self._access(node_u)
397
+ self._access(node_v)
398
+
399
+ # Get aggregate
400
+ if operation == "sum":
401
+ return node_v.path_sum
402
+ elif operation == "min":
403
+ return node_v.path_min
404
+ elif operation == "max":
405
+ return node_v.path_max
406
+ else:
407
+ raise XWNodeValueError(f"Unknown operation: {operation}")
408
+
409
+ # ============================================================================
410
+ # GRAPH OPERATIONS
411
+ # ============================================================================
412
+
413
+ def add_edge(self, source: str, target: str, edge_type: str = "default",
414
+ weight: float = 1.0, properties: Optional[Dict[str, Any]] = None,
415
+ is_bidirectional: bool = False, edge_id: Optional[str] = None) -> str:
416
+ """
417
+ Link vertices.
418
+
419
+ Args:
420
+ source: Source vertex
421
+ target: Target vertex
422
+ edge_type: Edge type
423
+ weight: Edge weight
424
+ properties: Edge properties
425
+ is_bidirectional: Bidirectional flag
426
+ edge_id: Edge ID
427
+
428
+ Returns:
429
+ Edge ID
430
+ """
431
+ self._link(source, target)
432
+
433
+ self._edges.add((source, target))
434
+ if is_bidirectional:
435
+ self._edges.add((target, source))
436
+
437
+ self._edge_count += 1
438
+
439
+ return edge_id or f"edge_{source}_{target}"
440
+
441
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
442
+ """Cut edge."""
443
+ if self._cut(source, target):
444
+ self._edges.discard((source, target))
445
+ self._edges.discard((target, source))
446
+ self._edge_count -= 1
447
+ return True
448
+ return False
449
+
450
+ def has_edge(self, source: str, target: str) -> bool:
451
+ """Check if edge exists."""
452
+ return (source, target) in self._edges
453
+
454
+ def is_connected(self, source: str, target: str, edge_type: Optional[str] = None) -> bool:
455
+ """Check if vertices connected."""
456
+ if source not in self._nodes or target not in self._nodes:
457
+ return False
458
+
459
+ return self._find_root(self._nodes[source]) == self._find_root(self._nodes[target])
460
+
461
+ def get_neighbors(self, node: str, edge_type: Optional[str] = None,
462
+ direction: str = "outgoing") -> List[str]:
463
+ """Get neighbors."""
464
+ neighbors = set()
465
+
466
+ for edge in self._edges:
467
+ if edge[0] == node:
468
+ neighbors.add(edge[1])
469
+ elif edge[1] == node:
470
+ neighbors.add(edge[0])
471
+
472
+ return list(neighbors)
473
+
474
+ def neighbors(self, node: str) -> Iterator[Any]:
475
+ """Get iterator over neighbors."""
476
+ return iter(self.get_neighbors(node))
477
+
478
+ def degree(self, node: str) -> int:
479
+ """Get degree of node."""
480
+ return len(self.get_neighbors(node))
481
+
482
+ def edges(self) -> Iterator[Tuple[Any, Any, Dict[str, Any]]]:
483
+ """Iterate over all edges with properties."""
484
+ for edge_dict in self.get_edges():
485
+ yield (edge_dict['source'], edge_dict['target'], {})
486
+
487
+ def vertices(self) -> Iterator[Any]:
488
+ """Get iterator over all vertices."""
489
+ return iter(self._vertices)
490
+
491
+ def get_edges(self, edge_type: Optional[str] = None, direction: str = "both") -> List[Dict[str, Any]]:
492
+ """Get all edges."""
493
+ seen = set()
494
+ edges = []
495
+
496
+ for u, v in self._edges:
497
+ if (u, v) not in seen and (v, u) not in seen:
498
+ seen.add((u, v))
499
+ edges.append({
500
+ 'source': u,
501
+ 'target': v,
502
+ 'edge_type': edge_type or 'tree'
503
+ })
504
+
505
+ return edges
506
+
507
+ def get_edge_data(self, source: str, target: str, edge_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
508
+ """Get edge data."""
509
+ if self.has_edge(source, target):
510
+ return {'source': source, 'target': target}
511
+ return None
512
+
513
+ # ============================================================================
514
+ # GRAPH ALGORITHMS
515
+ # ============================================================================
516
+
517
+ def shortest_path(self, source: str, target: str, edge_type: Optional[str] = None) -> List[str]:
518
+ """Find path (unique in tree)."""
519
+ if not self.is_connected(source, target):
520
+ return []
521
+
522
+ # BFS for simplicity (O(n) but simple)
523
+ queue = deque([source])
524
+ visited = {source}
525
+ parent = {source: None}
526
+
527
+ while queue:
528
+ current = queue.popleft()
529
+
530
+ if current == target:
531
+ path = []
532
+ while current:
533
+ path.append(current)
534
+ current = parent[current]
535
+ return list(reversed(path))
536
+
537
+ for neighbor in self.get_neighbors(current):
538
+ if neighbor not in visited:
539
+ visited.add(neighbor)
540
+ parent[neighbor] = current
541
+ queue.append(neighbor)
542
+
543
+ return []
544
+
545
+ def find_cycles(self, start_node: str, edge_type: Optional[str] = None, max_depth: int = 10) -> List[List[str]]:
546
+ """Find cycles (trees have no cycles)."""
547
+ return []
548
+
549
+ def traverse_graph(self, start_node: str, strategy: str = "bfs",
550
+ max_depth: int = 100, edge_type: Optional[str] = None) -> Iterator[str]:
551
+ """Traverse tree."""
552
+ if start_node not in self._vertices:
553
+ return
554
+
555
+ visited = set()
556
+ queue = deque([start_node])
557
+ visited.add(start_node)
558
+
559
+ while queue:
560
+ current = queue.popleft()
561
+ yield current
562
+
563
+ for neighbor in self.get_neighbors(current):
564
+ if neighbor not in visited:
565
+ visited.add(neighbor)
566
+ queue.append(neighbor)
567
+
568
+ # ============================================================================
569
+ # STANDARD OPERATIONS
570
+ # ============================================================================
571
+
572
+ def __len__(self) -> int:
573
+ """Get number of edges."""
574
+ return len(self._edges) // 2
575
+
576
+ def __iter__(self) -> Iterator[Dict[str, Any]]:
577
+ """Iterate over edges."""
578
+ return iter(self.get_edges())
579
+
580
+ def to_native(self) -> Dict[str, Any]:
581
+ """Convert to native representation."""
582
+ return {
583
+ 'vertices': list(self._vertices),
584
+ 'edges': self.get_edges()
585
+ }
586
+
587
+ # ============================================================================
588
+ # STATISTICS
589
+ # ============================================================================
590
+
591
+ def get_statistics(self) -> Dict[str, Any]:
592
+ """Get link-cut tree statistics."""
593
+ # Count trees (connected components)
594
+ roots = set()
595
+ for vertex in self._nodes.values():
596
+ roots.add(self._find_root(vertex).vertex)
597
+
598
+ return {
599
+ 'vertices': len(self._vertices),
600
+ 'edges': len(self),
601
+ 'trees': len(roots),
602
+ 'avg_tree_size': len(self._vertices) / max(len(roots), 1)
603
+ }
604
+
605
+ # ============================================================================
606
+ # UTILITY METHODS
607
+ # ============================================================================
608
+
609
+ @property
610
+ def strategy_name(self) -> str:
611
+ """Get strategy name."""
612
+ return "LINK_CUT"
613
+
614
+ @property
615
+ def supported_traits(self) -> List[EdgeTrait]:
616
+ """Get supported traits."""
617
+ return [EdgeTrait.DIRECTED, EdgeTrait.SPARSE]
618
+
619
+ def get_backend_info(self) -> Dict[str, Any]:
620
+ """Get backend information."""
621
+ return {
622
+ 'strategy': 'Link-Cut Trees',
623
+ 'description': 'Dynamic trees with path queries',
624
+ **self.get_statistics()
625
+ }
626
+