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.
Files changed (249) 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.24.dist-info/METADATA +900 -0
  120. exonware_xwnode-0.0.1.24.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/METADATA +0 -168
  242. exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
  243. /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
  244. /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
  245. /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
  246. /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
  247. /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
  248. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/WHEEL +0 -0
  249. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.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.24
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
+