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.
- exonware/__init__.py +8 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/base.py +7 -5
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +9 -9
- exonware/xwnode/common/management/migration.py +6 -6
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +7 -3
- exonware/xwnode/common/monitoring/pattern_detector.py +2 -2
- exonware/xwnode/common/monitoring/performance_monitor.py +6 -2
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/advisor.py +1 -1
- exonware/xwnode/common/patterns/flyweight.py +6 -2
- exonware/xwnode/common/patterns/registry.py +203 -184
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.21.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {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__(
|
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
|
+
|