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
@@ -24,9 +24,7 @@ with indexed operations.
24
24
 
25
25
  def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
26
26
  """Initialize the array list strategy."""
27
- super().__init__(data=None, **options)
28
- self._mode = NodeMode.ARRAY_LIST
29
- self._traits = traits
27
+ super().__init__(NodeMode.ARRAY_LIST, traits, **options)
30
28
  self._data: List[Any] = []
31
29
  self._size = 0
32
30
 
@@ -89,6 +87,41 @@ with indexed operations.
89
87
  # Return only non-None values in order
90
88
  return [value for value in self._data if value is not None]
91
89
 
90
+ def __len__(self) -> int:
91
+ """Get the number of items."""
92
+ return len(self._data)
93
+
94
+ def has(self, key: Any) -> bool:
95
+ """Check if value exists in array."""
96
+ return key in self._data
97
+
98
+ def get(self, key: Any, default: Any = None) -> Any:
99
+ """Get value by index or search for value."""
100
+ try:
101
+ index = int(key)
102
+ if 0 <= index < len(self._data):
103
+ return self._data[index]
104
+ except (ValueError, TypeError):
105
+ if key in self._data:
106
+ return key
107
+ return default
108
+
109
+ def put(self, key: Any, value: Any = None) -> None:
110
+ """Append value to array."""
111
+ self.insert(key, value if value is not None else key)
112
+
113
+ def keys(self) -> Iterator[Any]:
114
+ """Get all indices."""
115
+ return iter(range(len(self._data)))
116
+
117
+ def values(self) -> Iterator[Any]:
118
+ """Get all values."""
119
+ return iter(self._data)
120
+
121
+ def items(self) -> Iterator[tuple[Any, Any]]:
122
+ """Get all items as (index, value) pairs."""
123
+ return enumerate(self._data)
124
+
92
125
  # ============================================================================
93
126
  # LINEAR STRATEGY METHODS
94
127
  # ============================================================================
@@ -0,0 +1,581 @@
1
+ """
2
+ #exonware/xwnode/src/exonware/xwnode/nodes/strategies/node_art.py
3
+
4
+ ART (Adaptive Radix Tree) Node Strategy Implementation
5
+
6
+ This module implements the ART strategy for fast string key operations
7
+ with O(k) complexity where k = key length.
8
+
9
+ Company: eXonware.com
10
+ Author: Eng. Muhammad AlShehri
11
+ Email: connect@exonware.com
12
+ Version: 0.0.1.23
13
+ Generation Date: 11-Oct-2025
14
+ """
15
+
16
+ from typing import Any, Iterator, Dict, List, Optional, Union
17
+ from .base import ANodeStrategy
18
+ from ...defs import NodeMode, NodeTrait
19
+ from .contracts import NodeType
20
+ from ...common.utils import (
21
+ safe_to_native_conversion,
22
+ create_basic_metrics,
23
+ create_basic_backend_info,
24
+ create_size_tracker,
25
+ create_access_tracker,
26
+ update_size_tracker,
27
+ record_access,
28
+ get_access_metrics
29
+ )
30
+
31
+
32
+ class ARTNode:
33
+ """Base ART node with common functionality."""
34
+
35
+ def __init__(self):
36
+ self.prefix: bytes = b'' # Path compression
37
+ self.prefix_len: int = 0
38
+
39
+ def matches_prefix(self, key: bytes, depth: int) -> int:
40
+ """Check how many bytes of prefix match the key."""
41
+ matches = 0
42
+ for i in range(min(self.prefix_len, len(key) - depth)):
43
+ if self.prefix[i] == key[depth + i]:
44
+ matches += 1
45
+ else:
46
+ break
47
+ return matches
48
+
49
+
50
+ class ARTNode4(ARTNode):
51
+ """Node with up to 4 children - smallest node size."""
52
+
53
+ def __init__(self):
54
+ super().__init__()
55
+ self.keys: List[int] = [] # Byte values (0-255)
56
+ self.children: List[Any] = [] # Child nodes or leaf values
57
+
58
+ def find_child(self, byte: int) -> Optional[Any]:
59
+ """Find child by byte value."""
60
+ try:
61
+ idx = self.keys.index(byte)
62
+ return self.children[idx]
63
+ except ValueError:
64
+ return None
65
+
66
+ def add_child(self, byte: int, child: Any) -> bool:
67
+ """Add child if space available."""
68
+ if len(self.keys) >= 4:
69
+ return False
70
+ self.keys.append(byte)
71
+ self.children.append(child)
72
+ return True
73
+
74
+ def remove_child(self, byte: int) -> bool:
75
+ """Remove child by byte value."""
76
+ try:
77
+ idx = self.keys.index(byte)
78
+ self.keys.pop(idx)
79
+ self.children.pop(idx)
80
+ return True
81
+ except ValueError:
82
+ return False
83
+
84
+
85
+ class ARTNode16(ARTNode):
86
+ """Node with up to 16 children."""
87
+
88
+ def __init__(self):
89
+ super().__init__()
90
+ self.keys: List[int] = []
91
+ self.children: List[Any] = []
92
+
93
+ def find_child(self, byte: int) -> Optional[Any]:
94
+ """Find child by byte value."""
95
+ try:
96
+ idx = self.keys.index(byte)
97
+ return self.children[idx]
98
+ except ValueError:
99
+ return None
100
+
101
+ def add_child(self, byte: int, child: Any) -> bool:
102
+ """Add child if space available."""
103
+ if len(self.keys) >= 16:
104
+ return False
105
+ self.keys.append(byte)
106
+ self.children.append(child)
107
+ return True
108
+
109
+
110
+ class ARTNode48(ARTNode):
111
+ """Node with up to 48 children using index array."""
112
+
113
+ def __init__(self):
114
+ super().__init__()
115
+ # Index array: 256 bytes mapping byte->child_index
116
+ self.index: List[int] = [255] * 256 # 255 = empty
117
+ self.children: List[Any] = []
118
+
119
+ def find_child(self, byte: int) -> Optional[Any]:
120
+ """Find child by byte value."""
121
+ idx = self.index[byte]
122
+ if idx == 255:
123
+ return None
124
+ return self.children[idx]
125
+
126
+ def add_child(self, byte: int, child: Any) -> bool:
127
+ """Add child if space available."""
128
+ if len(self.children) >= 48:
129
+ return False
130
+ self.index[byte] = len(self.children)
131
+ self.children.append(child)
132
+ return True
133
+
134
+
135
+ class ARTNode256(ARTNode):
136
+ """Node with up to 256 children - direct array."""
137
+
138
+ def __init__(self):
139
+ super().__init__()
140
+ self.children: List[Optional[Any]] = [None] * 256
141
+
142
+ def find_child(self, byte: int) -> Optional[Any]:
143
+ """Find child by byte value."""
144
+ return self.children[byte]
145
+
146
+ def add_child(self, byte: int, child: Any) -> bool:
147
+ """Add child (always succeeds for Node256)."""
148
+ self.children[byte] = child
149
+ return True
150
+
151
+
152
+ class ARTStrategy(ANodeStrategy):
153
+ """
154
+ Adaptive Radix Tree - 3-10x faster than B-trees for string keys.
155
+
156
+ ART is a space-efficient and cache-friendly radix tree that adapts
157
+ node sizes based on the number of children (4, 16, 48, 256).
158
+
159
+ Features:
160
+ - O(k) operations where k = key length
161
+ - Path compression for space efficiency
162
+ - Adaptive node sizes
163
+ - Cache-friendly memory layout
164
+
165
+ Best for:
166
+ - String key lookups
167
+ - Prefix searches
168
+ - In-memory databases
169
+ - Route tables
170
+ """
171
+
172
+ # Strategy type classification
173
+ STRATEGY_TYPE = NodeType.TREE
174
+
175
+ def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
176
+ """Initialize the ART strategy."""
177
+ super().__init__(NodeMode.ART, traits, **options)
178
+ self._root: Optional[ARTNode] = None
179
+ self._size = 0
180
+ self._size_tracker = create_size_tracker()
181
+ self._access_tracker = create_access_tracker()
182
+
183
+ def get_supported_traits(self) -> NodeTrait:
184
+ """Get the traits supported by ART strategy."""
185
+ return NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.PREFIX_TREE
186
+
187
+ # ============================================================================
188
+ # CORE OPERATIONS
189
+ # ============================================================================
190
+
191
+ def _key_to_bytes(self, key: Any) -> bytes:
192
+ """Convert key to bytes for radix tree processing."""
193
+ if isinstance(key, bytes):
194
+ return key
195
+ elif isinstance(key, str):
196
+ return key.encode('utf-8')
197
+ else:
198
+ return str(key).encode('utf-8')
199
+
200
+ def _search(self, node: Optional[ARTNode], key: bytes, depth: int) -> Optional[Any]:
201
+ """Recursively search for key in tree."""
202
+ if node is None:
203
+ return None
204
+
205
+ # Check if this node has a value and we've consumed the full key
206
+ if depth >= len(key):
207
+ if hasattr(node, 'value'):
208
+ return node.value
209
+ return None
210
+
211
+ # Check prefix match
212
+ if node.prefix_len > 0:
213
+ matches = node.matches_prefix(key, depth)
214
+ if matches != node.prefix_len:
215
+ return None # Prefix mismatch
216
+ depth += node.prefix_len
217
+
218
+ # Re-check after advancing by prefix
219
+ if depth >= len(key):
220
+ if hasattr(node, 'value'):
221
+ return node.value
222
+ return None
223
+
224
+ # Continue search in child
225
+ byte = key[depth]
226
+ child = node.find_child(byte)
227
+
228
+ if child is None:
229
+ return None
230
+
231
+ # If child is a leaf value, return it
232
+ if not isinstance(child, ARTNode):
233
+ return child
234
+
235
+ return self._search(child, key, depth + 1)
236
+
237
+ def get(self, path: str, default: Any = None) -> Any:
238
+ """Retrieve a value by path (key)."""
239
+ record_access(self._access_tracker, 'get_count')
240
+
241
+ if '.' in path:
242
+ # Handle nested paths
243
+ parts = path.split('.')
244
+ current = self.get(parts[0])
245
+ for part in parts[1:]:
246
+ if isinstance(current, dict) and part in current:
247
+ current = current[part]
248
+ else:
249
+ return default
250
+ return current
251
+
252
+ key_bytes = self._key_to_bytes(path)
253
+ result = self._search(self._root, key_bytes, 0)
254
+ return result if result is not None else default
255
+
256
+ def _insert(self, node: Optional[ARTNode], key: bytes, value: Any, depth: int) -> ARTNode:
257
+ """Recursively insert key-value pair into tree."""
258
+ if node is None:
259
+ # Create new node and continue inserting
260
+ if depth >= len(key):
261
+ # We're at the end - create leaf with value
262
+ leaf = ARTNode4()
263
+ leaf.value = value
264
+ return leaf
265
+ else:
266
+ # Build tree structure for remaining bytes
267
+ new_node = ARTNode4()
268
+ # Insert the value at the current byte
269
+ byte = key[depth]
270
+ child = self._insert(None, key, value, depth + 1)
271
+ new_node.add_child(byte, child)
272
+ return new_node
273
+
274
+ # Check prefix match
275
+ if node.prefix_len > 0:
276
+ matches = node.matches_prefix(key, depth)
277
+ if matches < node.prefix_len:
278
+ # Need to split prefix
279
+ # Create new parent node
280
+ new_node = ARTNode4()
281
+ new_node.prefix = node.prefix[:matches]
282
+ new_node.prefix_len = matches
283
+
284
+ # Adjust old node prefix
285
+ old_byte = node.prefix[matches]
286
+ node.prefix = node.prefix[matches + 1:]
287
+ node.prefix_len -= matches + 1
288
+
289
+ # Add old node as child
290
+ new_node.add_child(old_byte, node)
291
+
292
+ # Add new value
293
+ if depth + matches < len(key):
294
+ new_byte = key[depth + matches]
295
+ leaf = ARTNode4()
296
+ leaf.value = value
297
+ new_node.add_child(new_byte, leaf)
298
+ else:
299
+ new_node.value = value
300
+
301
+ return new_node
302
+
303
+ depth += node.prefix_len
304
+
305
+ # Reached end of key
306
+ if depth >= len(key):
307
+ node.value = value
308
+ return node
309
+
310
+ # Insert into child
311
+ byte = key[depth]
312
+ child = node.find_child(byte)
313
+
314
+ if child is None:
315
+ # Create new child for this byte
316
+ child = self._insert(None, key, value, depth + 1)
317
+ if not node.add_child(byte, child):
318
+ # Node is full, need to grow
319
+ node = self._grow_node(node)
320
+ node.add_child(byte, child)
321
+ else:
322
+ if isinstance(child, ARTNode):
323
+ # Recurse into child
324
+ updated_child = self._insert(child, key, value, depth + 1)
325
+ # Update child reference
326
+ if isinstance(node, ARTNode4):
327
+ idx = node.keys.index(byte)
328
+ node.children[idx] = updated_child
329
+ elif isinstance(node, ARTNode16):
330
+ idx = node.keys.index(byte)
331
+ node.children[idx] = updated_child
332
+ elif isinstance(node, ARTNode48):
333
+ idx = node.index[byte]
334
+ node.children[idx] = updated_child
335
+ elif isinstance(node, ARTNode256):
336
+ node.children[byte] = updated_child
337
+ else:
338
+ # Child is a leaf value - need to create intermediate node
339
+ old_value = child
340
+ new_node = self._insert(None, key, value, depth + 1)
341
+
342
+ # Update child reference
343
+ if isinstance(node, ARTNode4):
344
+ idx = node.keys.index(byte)
345
+ node.children[idx] = new_node
346
+ elif isinstance(node, ARTNode16):
347
+ idx = node.keys.index(byte)
348
+ node.children[idx] = new_node
349
+ elif isinstance(node, ARTNode48):
350
+ idx = node.index[byte]
351
+ node.children[idx] = new_node
352
+ elif isinstance(node, ARTNode256):
353
+ node.children[byte] = new_node
354
+
355
+ return node
356
+
357
+ def _grow_node(self, node: ARTNode) -> ARTNode:
358
+ """Grow node to next size class."""
359
+ if isinstance(node, ARTNode4):
360
+ # Grow to Node16
361
+ new_node = ARTNode16()
362
+ new_node.prefix = node.prefix
363
+ new_node.prefix_len = node.prefix_len
364
+ new_node.keys = node.keys.copy()
365
+ new_node.children = node.children.copy()
366
+ if hasattr(node, 'value'):
367
+ new_node.value = node.value
368
+ return new_node
369
+ elif isinstance(node, ARTNode16):
370
+ # Grow to Node48
371
+ new_node = ARTNode48()
372
+ new_node.prefix = node.prefix
373
+ new_node.prefix_len = node.prefix_len
374
+ for i, byte in enumerate(node.keys):
375
+ new_node.index[byte] = i
376
+ new_node.children = node.children.copy()
377
+ if hasattr(node, 'value'):
378
+ new_node.value = node.value
379
+ return new_node
380
+ elif isinstance(node, ARTNode48):
381
+ # Grow to Node256
382
+ new_node = ARTNode256()
383
+ new_node.prefix = node.prefix
384
+ new_node.prefix_len = node.prefix_len
385
+ for byte in range(256):
386
+ idx = node.index[byte]
387
+ if idx != 255:
388
+ new_node.children[byte] = node.children[idx]
389
+ if hasattr(node, 'value'):
390
+ new_node.value = node.value
391
+ return new_node
392
+ else:
393
+ return node # Already at max size
394
+
395
+ def put(self, path: str, value: Any = None) -> 'ARTStrategy':
396
+ """Set a value at path."""
397
+ record_access(self._access_tracker, 'put_count')
398
+
399
+ if '.' in path:
400
+ # Handle nested paths by converting to dict
401
+ parts = path.split('.')
402
+ # Get or create root dict
403
+ root = self.get(parts[0])
404
+ if root is None:
405
+ root = {}
406
+ elif not isinstance(root, dict):
407
+ root = {parts[0]: root}
408
+
409
+ # Navigate and create nested structure
410
+ current = root
411
+ for part in parts[1:-1]:
412
+ if part not in current:
413
+ current[part] = {}
414
+ current = current[part]
415
+ current[parts[-1]] = value
416
+
417
+ # Store the root dict
418
+ key_bytes = self._key_to_bytes(parts[0])
419
+ key_existed = self.exists(parts[0])
420
+ self._root = self._insert(self._root, key_bytes, root, 0)
421
+ if not key_existed:
422
+ update_size_tracker(self._size_tracker, 1)
423
+ self._size += 1
424
+ else:
425
+ key_bytes = self._key_to_bytes(path)
426
+ key_existed = self.exists(path)
427
+ self._root = self._insert(self._root, key_bytes, value, 0)
428
+ if not key_existed:
429
+ update_size_tracker(self._size_tracker, 1)
430
+ self._size += 1
431
+
432
+ return self
433
+
434
+ def has(self, key: Any) -> bool:
435
+ """Check if key exists."""
436
+ return self.get(str(key)) is not None
437
+
438
+ def exists(self, path: str) -> bool:
439
+ """Check if path exists."""
440
+ return self.get(path) is not None
441
+
442
+ def delete(self, key: Any) -> bool:
443
+ """Remove a key-value pair."""
444
+ # Simplified deletion - mark as deleted
445
+ key_str = str(key)
446
+ if self.exists(key_str):
447
+ # In a full implementation, we would remove the node
448
+ # For now, we set to None
449
+ self.put(key_str, None)
450
+ update_size_tracker(self._size_tracker, -1)
451
+ record_access(self._access_tracker, 'delete_count')
452
+ self._size -= 1
453
+ return True
454
+ return False
455
+
456
+ def remove(self, key: Any) -> bool:
457
+ """Remove a key-value pair (alias for delete)."""
458
+ return self.delete(key)
459
+
460
+ # ============================================================================
461
+ # ITERATION METHODS
462
+ # ============================================================================
463
+
464
+ def _collect_all(self, node: Optional[ARTNode], prefix: bytes) -> List[tuple[bytes, Any]]:
465
+ """Collect all key-value pairs from tree."""
466
+ if node is None:
467
+ return []
468
+
469
+ results = []
470
+
471
+ # Check if node has a value
472
+ if hasattr(node, 'value') and node.value is not None:
473
+ results.append((prefix, node.value))
474
+
475
+ # Collect from children
476
+ if isinstance(node, ARTNode4):
477
+ for i, byte in enumerate(node.keys):
478
+ child = node.children[i]
479
+ child_prefix = prefix + bytes([byte])
480
+ if isinstance(child, ARTNode):
481
+ results.extend(self._collect_all(child, child_prefix))
482
+ else:
483
+ results.append((child_prefix, child))
484
+ elif isinstance(node, ARTNode16):
485
+ for i, byte in enumerate(node.keys):
486
+ child = node.children[i]
487
+ child_prefix = prefix + bytes([byte])
488
+ if isinstance(child, ARTNode):
489
+ results.extend(self._collect_all(child, child_prefix))
490
+ else:
491
+ results.append((child_prefix, child))
492
+ elif isinstance(node, ARTNode48):
493
+ for byte in range(256):
494
+ idx = node.index[byte]
495
+ if idx != 255:
496
+ child = node.children[idx]
497
+ child_prefix = prefix + bytes([byte])
498
+ if isinstance(child, ARTNode):
499
+ results.extend(self._collect_all(child, child_prefix))
500
+ else:
501
+ results.append((child_prefix, child))
502
+ elif isinstance(node, ARTNode256):
503
+ for byte in range(256):
504
+ child = node.children[byte]
505
+ if child is not None:
506
+ child_prefix = prefix + bytes([byte])
507
+ if isinstance(child, ARTNode):
508
+ results.extend(self._collect_all(child, child_prefix))
509
+ else:
510
+ results.append((child_prefix, child))
511
+
512
+ return results
513
+
514
+ def keys(self) -> Iterator[Any]:
515
+ """Get an iterator over all keys."""
516
+ all_items = self._collect_all(self._root, b'')
517
+ for key_bytes, _ in all_items:
518
+ try:
519
+ yield key_bytes.decode('utf-8')
520
+ except UnicodeDecodeError:
521
+ yield key_bytes
522
+
523
+ def values(self) -> Iterator[Any]:
524
+ """Get an iterator over all values."""
525
+ all_items = self._collect_all(self._root, b'')
526
+ for _, value in all_items:
527
+ yield value
528
+
529
+ def items(self) -> Iterator[tuple[Any, Any]]:
530
+ """Get an iterator over all key-value pairs."""
531
+ all_items = self._collect_all(self._root, b'')
532
+ for key_bytes, value in all_items:
533
+ try:
534
+ key = key_bytes.decode('utf-8')
535
+ except UnicodeDecodeError:
536
+ key = key_bytes
537
+ yield (key, value)
538
+
539
+ def __len__(self) -> int:
540
+ """Get the number of key-value pairs."""
541
+ return self._size
542
+
543
+ # ============================================================================
544
+ # ADVANCED FEATURES
545
+ # ============================================================================
546
+
547
+ def prefix_search(self, prefix: str) -> List[tuple[str, Any]]:
548
+ """
549
+ Search for all keys with given prefix.
550
+
551
+ This is a key advantage of ART - efficient prefix searches.
552
+ """
553
+ prefix_bytes = self._key_to_bytes(prefix)
554
+ # Simplified: collect all and filter
555
+ all_items = self._collect_all(self._root, b'')
556
+ results = []
557
+ for key_bytes, value in all_items:
558
+ if key_bytes.startswith(prefix_bytes):
559
+ try:
560
+ key = key_bytes.decode('utf-8')
561
+ except UnicodeDecodeError:
562
+ key = str(key_bytes)
563
+ results.append((key, value))
564
+ return results
565
+
566
+ def to_native(self) -> Dict[str, Any]:
567
+ """Convert to native Python dictionary."""
568
+ result = {}
569
+ for key, value in self.items():
570
+ result[str(key)] = safe_to_native_conversion(value)
571
+ return result
572
+
573
+ def get_backend_info(self) -> Dict[str, Any]:
574
+ """Get backend information."""
575
+ return {
576
+ **create_basic_backend_info('ART', 'Adaptive Radix Tree'),
577
+ 'total_keys': self._size,
578
+ **self._size_tracker,
579
+ **get_access_metrics(self._access_tracker)
580
+ }
581
+