exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exonware/__init__.py +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- exonware_xwnode-0.0.1.24.dist-info/METADATA +900 -0
- exonware_xwnode-0.0.1.24.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/METADATA +0 -168
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/licenses/LICENSE +0 -0
@@ -13,15 +13,72 @@ from ...defs import NodeMode, NodeTrait
|
|
13
13
|
|
14
14
|
class FenwickTreeStrategy(ANodeTreeStrategy):
|
15
15
|
"""
|
16
|
-
Fenwick Tree
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
Fenwick Tree (Binary Indexed Tree) strategy for efficient prefix sums.
|
17
|
+
|
18
|
+
WHY Fenwick Tree:
|
19
|
+
- Simpler than Segment Tree for prefix sum queries
|
20
|
+
- O(log n) for both query and update (vs O(n) for naive)
|
21
|
+
- Minimal memory overhead: just O(n) array
|
22
|
+
- Elegant bit manipulation (lowest set bit operations)
|
23
|
+
- Industry standard for competitive programming
|
24
|
+
|
25
|
+
WHY this implementation:
|
26
|
+
- 1-indexed array (standard for Fenwick/BIT - simplifies bit ops)
|
27
|
+
- Lowest set bit technique: `idx & -idx` for parent/child navigation
|
28
|
+
- Delta-based updates (add/subtract differences)
|
29
|
+
- Supports cumulative frequency applications
|
30
|
+
|
31
|
+
Time Complexity:
|
32
|
+
- Prefix Sum: O(log n) - traverse parents via bit operations
|
33
|
+
- Range Sum: O(log n) - difference of two prefix sums
|
34
|
+
- Point Update: O(log n) - update ancestors via bit operations
|
35
|
+
- Build: O(n log n) - n updates
|
36
|
+
|
37
|
+
Space Complexity: O(n) - single array, very memory efficient
|
38
|
+
|
39
|
+
Trade-offs:
|
40
|
+
- Advantage: Simpler than Segment Tree (fewer lines of code)
|
41
|
+
- Advantage: Lower memory overhead than Segment Tree
|
42
|
+
- Limitation: Only supports PREFIX operations (not arbitrary ranges as efficiently)
|
43
|
+
- Limitation: Requires associative, invertible operations (sum, XOR work; min/max don't)
|
44
|
+
- Compared to Segment Tree: Simpler and faster for prefix sums, less flexible
|
45
|
+
- Compared to prefix sum array: Dynamic updates O(log n) vs O(n)
|
46
|
+
|
47
|
+
Best for:
|
48
|
+
- Prefix sum queries (cumulative frequencies)
|
49
|
+
- Competitive programming problems
|
50
|
+
- When space is limited (O(n) vs O(4n) for Segment Tree)
|
51
|
+
- Invertible operations (sum, XOR, but NOT min/max)
|
52
|
+
- Dynamic arrays requiring frequent sum queries
|
53
|
+
|
54
|
+
Not recommended for:
|
55
|
+
- Non-invertible operations (min, max, GCD) - use Segment Tree
|
56
|
+
- When arbitrary range operations are primary need
|
57
|
+
- When simpler solutions suffice (e.g., static prefix array)
|
58
|
+
- 2D/multidimensional ranges (use 2D Fenwick or Segment Tree)
|
59
|
+
|
60
|
+
Following eXonware Priorities:
|
61
|
+
1. Usability: Clean API for prefix/range sums
|
62
|
+
2. Maintainability: Simple bit manipulation logic, well-documented
|
63
|
+
3. Performance: O(log n) operations with low constants
|
64
|
+
4. Extensibility: Can extend to 2D Fenwick Tree
|
65
|
+
5. Security: Input validation on all operations
|
66
|
+
|
67
|
+
Industry Best Practices:
|
68
|
+
- Follows Peter Fenwick's original paper (1994)
|
69
|
+
- Uses 1-indexed array (standard for BIT)
|
70
|
+
- Lowest set bit operations: `i & -i`
|
71
|
+
- Parent navigation: `i += i & -i`
|
72
|
+
- Child navigation: `i -= i & -i`
|
73
|
+
|
74
|
+
Performance Note:
|
75
|
+
Fenwick Trees excel at prefix sums with O(log n) query and update.
|
76
|
+
For arbitrary range operations (especially min/max), use Segment Tree.
|
77
|
+
Trade-off: Simplicity (Fenwick) vs Flexibility (Segment Tree).
|
78
|
+
"""
|
20
79
|
|
21
80
|
# Strategy type classification
|
22
81
|
STRATEGY_TYPE = NodeType.TREE
|
23
|
-
minimal memory overhead.
|
24
|
-
"""
|
25
82
|
|
26
83
|
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
27
84
|
"""Initialize the Fenwick Tree strategy."""
|
@@ -159,6 +216,34 @@ minimal memory overhead.
|
|
159
216
|
new_size = old_size * 2
|
160
217
|
self._tree.extend([0.0] * (new_size - old_size))
|
161
218
|
|
219
|
+
def update(self, index: int, value: float) -> None:
|
220
|
+
"""
|
221
|
+
Update value at specific index (1-indexed).
|
222
|
+
|
223
|
+
Sets the value at index to the new value by calculating delta.
|
224
|
+
"""
|
225
|
+
if index < 1:
|
226
|
+
raise ValueError(f"Fenwick Tree indices must be >= 1, got {index}")
|
227
|
+
|
228
|
+
# Ensure tree is large enough
|
229
|
+
while index >= len(self._tree):
|
230
|
+
self._resize_tree()
|
231
|
+
|
232
|
+
# Calculate delta from current value
|
233
|
+
current = self._get_point_value(index)
|
234
|
+
delta = value - current
|
235
|
+
|
236
|
+
# Update tree with delta
|
237
|
+
self._update_point(index, delta)
|
238
|
+
|
239
|
+
# Store for retrieval
|
240
|
+
key = str(index)
|
241
|
+
if key not in self._indices:
|
242
|
+
self._indices[key] = index
|
243
|
+
self._reverse_indices[index] = key
|
244
|
+
self._size += 1
|
245
|
+
self._values[key] = value
|
246
|
+
|
162
247
|
def _update_point(self, idx: int, delta: float) -> None:
|
163
248
|
"""Add delta to position idx (1-indexed)."""
|
164
249
|
while idx < len(self._tree):
|
@@ -184,25 +269,32 @@ minimal memory overhead.
|
|
184
269
|
return result
|
185
270
|
|
186
271
|
def prefix_sum(self, index: int) -> float:
|
187
|
-
"""
|
188
|
-
|
272
|
+
"""
|
273
|
+
Get prefix sum from 1 to index (1-indexed, inclusive).
|
274
|
+
|
275
|
+
For Fenwick Tree, index 1 is the first element.
|
276
|
+
To query sum of first 3 elements, use prefix_sum(3).
|
277
|
+
"""
|
278
|
+
if index < 1:
|
189
279
|
return 0.0
|
190
280
|
|
191
|
-
#
|
192
|
-
|
281
|
+
# Ensure index is within bounds
|
282
|
+
if index >= len(self._tree):
|
283
|
+
index = len(self._tree) - 1
|
284
|
+
|
285
|
+
return self._prefix_sum(index)
|
193
286
|
|
194
287
|
def range_sum(self, left: int, right: int) -> float:
|
195
|
-
"""
|
196
|
-
|
197
|
-
return 0.0
|
288
|
+
"""
|
289
|
+
Get sum of elements in range [left, right] (1-indexed, inclusive).
|
198
290
|
|
199
|
-
|
200
|
-
|
291
|
+
For Fenwick Tree, indices start at 1 (standard for BIT).
|
292
|
+
To query sum of elements 2-5, use range_sum(2, 5).
|
293
|
+
"""
|
294
|
+
if left > right or left < 1:
|
295
|
+
return 0.0
|
201
296
|
|
202
|
-
|
203
|
-
return self._prefix_sum(right + 1)
|
204
|
-
else:
|
205
|
-
return self._prefix_sum(right + 1) - self._prefix_sum(left)
|
297
|
+
return self._prefix_sum(right) - self._prefix_sum(left - 1)
|
206
298
|
|
207
299
|
def point_update(self, index: int, new_value: float) -> None:
|
208
300
|
"""Update value at index (0-indexed)."""
|
@@ -0,0 +1,403 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/node_hamt.py
|
3
|
+
|
4
|
+
HAMT (Hash Array Mapped Trie) Node Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the HAMT strategy for persistent data structures
|
7
|
+
with structural sharing and efficient immutable operations.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.24
|
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 HAMTNode:
|
33
|
+
"""
|
34
|
+
HAMT Node with bitmap-based indexing.
|
35
|
+
|
36
|
+
Each node uses a 32-bit bitmap to track which slots are occupied,
|
37
|
+
and stores only the occupied slots in a compact array.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self):
|
41
|
+
self.bitmap: int = 0 # 32-bit bitmap for tracking occupied slots
|
42
|
+
self.children: List[Any] = [] # Compact array of children/values
|
43
|
+
|
44
|
+
def index_for_bit(self, bit_pos: int) -> int:
|
45
|
+
"""Calculate array index for given bit position using popcount."""
|
46
|
+
# Count number of 1s before bit_pos
|
47
|
+
mask = (1 << bit_pos) - 1
|
48
|
+
return bin(self.bitmap & mask).count('1')
|
49
|
+
|
50
|
+
def has_child(self, bit_pos: int) -> bool:
|
51
|
+
"""Check if child exists at bit position."""
|
52
|
+
return (self.bitmap & (1 << bit_pos)) != 0
|
53
|
+
|
54
|
+
def get_child(self, bit_pos: int) -> Optional[Any]:
|
55
|
+
"""Get child at bit position."""
|
56
|
+
if not self.has_child(bit_pos):
|
57
|
+
return None
|
58
|
+
idx = self.index_for_bit(bit_pos)
|
59
|
+
return self.children[idx]
|
60
|
+
|
61
|
+
def set_child(self, bit_pos: int, value: Any) -> 'HAMTNode':
|
62
|
+
"""
|
63
|
+
Set child at bit position (immutable - returns new node).
|
64
|
+
|
65
|
+
This creates a new node with structural sharing.
|
66
|
+
"""
|
67
|
+
new_node = HAMTNode()
|
68
|
+
new_node.bitmap = self.bitmap
|
69
|
+
new_node.children = self.children.copy()
|
70
|
+
|
71
|
+
if self.has_child(bit_pos):
|
72
|
+
# Update existing child
|
73
|
+
idx = self.index_for_bit(bit_pos)
|
74
|
+
new_node.children[idx] = value
|
75
|
+
else:
|
76
|
+
# Insert new child
|
77
|
+
new_node.bitmap |= (1 << bit_pos)
|
78
|
+
idx = self.index_for_bit(bit_pos)
|
79
|
+
new_node.children.insert(idx, value)
|
80
|
+
|
81
|
+
return new_node
|
82
|
+
|
83
|
+
def remove_child(self, bit_pos: int) -> 'HAMTNode':
|
84
|
+
"""Remove child at bit position (immutable - returns new node)."""
|
85
|
+
if not self.has_child(bit_pos):
|
86
|
+
return self # No change
|
87
|
+
|
88
|
+
new_node = HAMTNode()
|
89
|
+
new_node.bitmap = self.bitmap & ~(1 << bit_pos)
|
90
|
+
new_node.children = self.children.copy()
|
91
|
+
idx = self.index_for_bit(bit_pos)
|
92
|
+
new_node.children.pop(idx)
|
93
|
+
|
94
|
+
return new_node
|
95
|
+
|
96
|
+
|
97
|
+
class HAMTLeaf:
|
98
|
+
"""Leaf node storing key-value pair."""
|
99
|
+
|
100
|
+
def __init__(self, key: Any, value: Any):
|
101
|
+
self.key = key
|
102
|
+
self.value = value
|
103
|
+
|
104
|
+
|
105
|
+
class HAMTStrategy(ANodeStrategy):
|
106
|
+
"""
|
107
|
+
HAMT (Hash Array Mapped Trie) - Persistent data structure.
|
108
|
+
|
109
|
+
HAMT is a persistent hash table that uses structural sharing
|
110
|
+
for efficient immutable operations. Popular in functional
|
111
|
+
programming languages like Clojure, Scala, and Haskell.
|
112
|
+
|
113
|
+
Features:
|
114
|
+
- Persistent/immutable operations
|
115
|
+
- Structural sharing (memory efficient)
|
116
|
+
- O(log32 n) operations (5-bit chunks)
|
117
|
+
- Cache-friendly bitmap indexing
|
118
|
+
- No rehashing needed
|
119
|
+
|
120
|
+
Best for:
|
121
|
+
- Functional programming
|
122
|
+
- Immutable data structures
|
123
|
+
- Version control systems
|
124
|
+
- Undo/redo functionality
|
125
|
+
- Concurrent read-heavy workloads
|
126
|
+
"""
|
127
|
+
|
128
|
+
# Strategy type classification
|
129
|
+
STRATEGY_TYPE = NodeType.TREE
|
130
|
+
|
131
|
+
# HAMT constants
|
132
|
+
BITS_PER_LEVEL = 5 # 2^5 = 32 children per node
|
133
|
+
LEVEL_MASK = 0x1F # 0b11111
|
134
|
+
|
135
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
136
|
+
"""Initialize the HAMT strategy."""
|
137
|
+
super().__init__(NodeMode.HAMT, traits, **options)
|
138
|
+
self._root: HAMTNode = HAMTNode()
|
139
|
+
self._size = 0
|
140
|
+
self._size_tracker = create_size_tracker()
|
141
|
+
self._access_tracker = create_access_tracker()
|
142
|
+
|
143
|
+
def get_supported_traits(self) -> NodeTrait:
|
144
|
+
"""Get the traits supported by HAMT strategy."""
|
145
|
+
return NodeTrait.INDEXED | NodeTrait.PERSISTENT
|
146
|
+
|
147
|
+
# ============================================================================
|
148
|
+
# CORE OPERATIONS
|
149
|
+
# ============================================================================
|
150
|
+
|
151
|
+
def _hash_key(self, key: Any) -> int:
|
152
|
+
"""Compute hash for key."""
|
153
|
+
return hash(str(key)) & 0xFFFFFFFF # 32-bit hash
|
154
|
+
|
155
|
+
def _get_chunk(self, hash_val: int, level: int) -> int:
|
156
|
+
"""Extract 5-bit chunk at given level."""
|
157
|
+
shift = level * self.BITS_PER_LEVEL
|
158
|
+
return (hash_val >> shift) & self.LEVEL_MASK
|
159
|
+
|
160
|
+
def _search(self, node: HAMTNode, key: Any, hash_val: int, level: int) -> Optional[Any]:
|
161
|
+
"""Recursively search for key in HAMT."""
|
162
|
+
chunk = self._get_chunk(hash_val, level)
|
163
|
+
|
164
|
+
if not node.has_child(chunk):
|
165
|
+
return None
|
166
|
+
|
167
|
+
child = node.get_child(chunk)
|
168
|
+
|
169
|
+
if isinstance(child, HAMTLeaf):
|
170
|
+
if child.key == key:
|
171
|
+
return child.value
|
172
|
+
return None
|
173
|
+
elif isinstance(child, HAMTNode):
|
174
|
+
return self._search(child, key, hash_val, level + 1)
|
175
|
+
|
176
|
+
return None
|
177
|
+
|
178
|
+
def get(self, path: str, default: Any = None) -> Any:
|
179
|
+
"""Retrieve a value by path."""
|
180
|
+
record_access(self._access_tracker, 'get_count')
|
181
|
+
|
182
|
+
if '.' in path:
|
183
|
+
# Handle nested paths
|
184
|
+
parts = path.split('.')
|
185
|
+
current = self.get(parts[0])
|
186
|
+
for part in parts[1:]:
|
187
|
+
if isinstance(current, dict) and part in current:
|
188
|
+
current = current[part]
|
189
|
+
else:
|
190
|
+
return default
|
191
|
+
return current
|
192
|
+
|
193
|
+
hash_val = self._hash_key(path)
|
194
|
+
result = self._search(self._root, path, hash_val, 0)
|
195
|
+
return result if result is not None else default
|
196
|
+
|
197
|
+
def _insert(self, node: HAMTNode, key: Any, value: Any,
|
198
|
+
hash_val: int, level: int) -> HAMTNode:
|
199
|
+
"""
|
200
|
+
Recursively insert key-value pair (immutable).
|
201
|
+
|
202
|
+
Returns new node with structural sharing.
|
203
|
+
"""
|
204
|
+
chunk = self._get_chunk(hash_val, level)
|
205
|
+
|
206
|
+
if not node.has_child(chunk):
|
207
|
+
# Empty slot - create leaf
|
208
|
+
leaf = HAMTLeaf(key, value)
|
209
|
+
return node.set_child(chunk, leaf)
|
210
|
+
|
211
|
+
child = node.get_child(chunk)
|
212
|
+
|
213
|
+
if isinstance(child, HAMTLeaf):
|
214
|
+
if child.key == key:
|
215
|
+
# Update existing key
|
216
|
+
new_leaf = HAMTLeaf(key, value)
|
217
|
+
return node.set_child(chunk, new_leaf)
|
218
|
+
else:
|
219
|
+
# Hash collision - create sub-node
|
220
|
+
new_subnode = HAMTNode()
|
221
|
+
|
222
|
+
# Insert existing leaf
|
223
|
+
existing_hash = self._hash_key(child.key)
|
224
|
+
existing_chunk = self._get_chunk(existing_hash, level + 1)
|
225
|
+
new_subnode = new_subnode.set_child(existing_chunk, child)
|
226
|
+
|
227
|
+
# Insert new leaf
|
228
|
+
new_chunk = self._get_chunk(hash_val, level + 1)
|
229
|
+
new_leaf = HAMTLeaf(key, value)
|
230
|
+
new_subnode = new_subnode.set_child(new_chunk, new_leaf)
|
231
|
+
|
232
|
+
return node.set_child(chunk, new_subnode)
|
233
|
+
|
234
|
+
elif isinstance(child, HAMTNode):
|
235
|
+
# Recurse into sub-node
|
236
|
+
new_child = self._insert(child, key, value, hash_val, level + 1)
|
237
|
+
return node.set_child(chunk, new_child)
|
238
|
+
|
239
|
+
return node
|
240
|
+
|
241
|
+
def put(self, path: str, value: Any = None) -> 'HAMTStrategy':
|
242
|
+
"""Set a value at path (immutable operation)."""
|
243
|
+
record_access(self._access_tracker, 'put_count')
|
244
|
+
|
245
|
+
if '.' in path:
|
246
|
+
# Handle nested paths
|
247
|
+
parts = path.split('.')
|
248
|
+
root = self.get(parts[0])
|
249
|
+
if root is None:
|
250
|
+
root = {}
|
251
|
+
elif not isinstance(root, dict):
|
252
|
+
root = {parts[0]: root}
|
253
|
+
|
254
|
+
current = root
|
255
|
+
for part in parts[1:-1]:
|
256
|
+
if part not in current:
|
257
|
+
current[part] = {}
|
258
|
+
current = current[part]
|
259
|
+
current[parts[-1]] = value
|
260
|
+
|
261
|
+
# Create new root (structural sharing)
|
262
|
+
hash_val = self._hash_key(parts[0])
|
263
|
+
self._root = self._insert(self._root, parts[0], root, hash_val, 0)
|
264
|
+
else:
|
265
|
+
exists = self.exists(path)
|
266
|
+
hash_val = self._hash_key(path)
|
267
|
+
self._root = self._insert(self._root, path, value, hash_val, 0)
|
268
|
+
|
269
|
+
if not exists:
|
270
|
+
update_size_tracker(self._size_tracker, 1)
|
271
|
+
self._size += 1
|
272
|
+
|
273
|
+
return self
|
274
|
+
|
275
|
+
def _remove(self, node: HAMTNode, key: Any, hash_val: int, level: int) -> Optional[HAMTNode]:
|
276
|
+
"""Recursively remove key (immutable)."""
|
277
|
+
chunk = self._get_chunk(hash_val, level)
|
278
|
+
|
279
|
+
if not node.has_child(chunk):
|
280
|
+
return node # Key not found
|
281
|
+
|
282
|
+
child = node.get_child(chunk)
|
283
|
+
|
284
|
+
if isinstance(child, HAMTLeaf):
|
285
|
+
if child.key == key:
|
286
|
+
# Remove leaf
|
287
|
+
return node.remove_child(chunk)
|
288
|
+
return node # Different key
|
289
|
+
|
290
|
+
elif isinstance(child, HAMTNode):
|
291
|
+
# Recurse
|
292
|
+
new_child = self._remove(child, key, hash_val, level + 1)
|
293
|
+
if new_child is None or (new_child.bitmap == 0):
|
294
|
+
# Child is now empty, remove it
|
295
|
+
return node.remove_child(chunk)
|
296
|
+
else:
|
297
|
+
return node.set_child(chunk, new_child)
|
298
|
+
|
299
|
+
return node
|
300
|
+
|
301
|
+
def delete(self, key: Any) -> bool:
|
302
|
+
"""Remove a key-value pair (immutable operation)."""
|
303
|
+
key_str = str(key)
|
304
|
+
if self.exists(key_str):
|
305
|
+
hash_val = self._hash_key(key_str)
|
306
|
+
self._root = self._remove(self._root, key_str, hash_val, 0)
|
307
|
+
update_size_tracker(self._size_tracker, -1)
|
308
|
+
record_access(self._access_tracker, 'delete_count')
|
309
|
+
self._size -= 1
|
310
|
+
return True
|
311
|
+
return False
|
312
|
+
|
313
|
+
def remove(self, key: Any) -> bool:
|
314
|
+
"""Remove a key-value pair (alias for delete)."""
|
315
|
+
return self.delete(key)
|
316
|
+
|
317
|
+
def has(self, key: Any) -> bool:
|
318
|
+
"""Check if key exists."""
|
319
|
+
return self.get(str(key)) is not None
|
320
|
+
|
321
|
+
def exists(self, path: str) -> bool:
|
322
|
+
"""Check if path exists."""
|
323
|
+
return self.get(path) is not None
|
324
|
+
|
325
|
+
# ============================================================================
|
326
|
+
# ITERATION METHODS
|
327
|
+
# ============================================================================
|
328
|
+
|
329
|
+
def _collect_all(self, node: HAMTNode) -> List[tuple[Any, Any]]:
|
330
|
+
"""Collect all key-value pairs from HAMT."""
|
331
|
+
results = []
|
332
|
+
|
333
|
+
for child in node.children:
|
334
|
+
if isinstance(child, HAMTLeaf):
|
335
|
+
results.append((child.key, child.value))
|
336
|
+
elif isinstance(child, HAMTNode):
|
337
|
+
results.extend(self._collect_all(child))
|
338
|
+
|
339
|
+
return results
|
340
|
+
|
341
|
+
def keys(self) -> Iterator[Any]:
|
342
|
+
"""Get an iterator over all keys."""
|
343
|
+
for key, _ in self._collect_all(self._root):
|
344
|
+
yield key
|
345
|
+
|
346
|
+
def values(self) -> Iterator[Any]:
|
347
|
+
"""Get an iterator over all values."""
|
348
|
+
for _, value in self._collect_all(self._root):
|
349
|
+
yield value
|
350
|
+
|
351
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
352
|
+
"""Get an iterator over all key-value pairs."""
|
353
|
+
for item in self._collect_all(self._root):
|
354
|
+
yield item
|
355
|
+
|
356
|
+
def __len__(self) -> int:
|
357
|
+
"""Get the number of key-value pairs."""
|
358
|
+
return self._size
|
359
|
+
|
360
|
+
# ============================================================================
|
361
|
+
# ADVANCED FEATURES
|
362
|
+
# ============================================================================
|
363
|
+
|
364
|
+
def snapshot(self) -> 'HAMTStrategy':
|
365
|
+
"""
|
366
|
+
Create immutable snapshot of current state.
|
367
|
+
|
368
|
+
Due to structural sharing, this is very efficient (O(1)).
|
369
|
+
"""
|
370
|
+
new_strategy = HAMTStrategy(self.traits, **self.options)
|
371
|
+
new_strategy._root = self._root # Shared structure
|
372
|
+
new_strategy._size = self._size
|
373
|
+
return new_strategy
|
374
|
+
|
375
|
+
def get_tree_depth(self) -> int:
|
376
|
+
"""Calculate maximum tree depth."""
|
377
|
+
def depth(node: HAMTNode, current: int) -> int:
|
378
|
+
max_d = current
|
379
|
+
for child in node.children:
|
380
|
+
if isinstance(child, HAMTNode):
|
381
|
+
max_d = max(max_d, depth(child, current + 1))
|
382
|
+
return max_d
|
383
|
+
|
384
|
+
return depth(self._root, 0)
|
385
|
+
|
386
|
+
def to_native(self) -> Dict[str, Any]:
|
387
|
+
"""Convert to native Python dictionary."""
|
388
|
+
result = {}
|
389
|
+
for key, value in self.items():
|
390
|
+
result[str(key)] = safe_to_native_conversion(value)
|
391
|
+
return result
|
392
|
+
|
393
|
+
def get_backend_info(self) -> Dict[str, Any]:
|
394
|
+
"""Get backend information."""
|
395
|
+
return {
|
396
|
+
**create_basic_backend_info('HAMT', 'Hash Array Mapped Trie'),
|
397
|
+
'total_keys': self._size,
|
398
|
+
'tree_depth': self.get_tree_depth(),
|
399
|
+
'bits_per_level': self.BITS_PER_LEVEL,
|
400
|
+
**self._size_tracker,
|
401
|
+
**get_access_metrics(self._access_tracker)
|
402
|
+
}
|
403
|
+
|