exonware-xwnode 0.0.1.22__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 +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.22.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.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.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,742 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/interval_tree.py
|
3
|
+
|
4
|
+
Interval Tree Node Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the INTERVAL_TREE strategy for efficient interval
|
7
|
+
overlap queries using augmented balanced trees.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.23
|
13
|
+
Generation Date: 12-Oct-2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple
|
17
|
+
from .base import ANodeTreeStrategy
|
18
|
+
from .contracts import NodeType
|
19
|
+
from ...defs import NodeMode, NodeTrait
|
20
|
+
from ...errors import XWNodeError, XWNodeValueError
|
21
|
+
|
22
|
+
|
23
|
+
class Interval:
|
24
|
+
"""
|
25
|
+
Interval representation [low, high].
|
26
|
+
|
27
|
+
WHY dedicated interval class:
|
28
|
+
- Encapsulates interval logic
|
29
|
+
- Provides clean comparison operations
|
30
|
+
- Supports closed, open, half-open intervals
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, low: float, high: float, value: Any = None):
|
34
|
+
"""
|
35
|
+
Initialize interval.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
low: Lower bound
|
39
|
+
high: Upper bound
|
40
|
+
value: Associated data
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
XWNodeValueError: If low > high
|
44
|
+
"""
|
45
|
+
if low > high:
|
46
|
+
raise XWNodeValueError(f"Invalid interval: low ({low}) > high ({high})")
|
47
|
+
|
48
|
+
self.low = low
|
49
|
+
self.high = high
|
50
|
+
self.value = value
|
51
|
+
|
52
|
+
def overlaps(self, other: 'Interval') -> bool:
|
53
|
+
"""Check if this interval overlaps with another."""
|
54
|
+
return self.low <= other.high and other.low <= self.high
|
55
|
+
|
56
|
+
def contains_point(self, point: float) -> bool:
|
57
|
+
"""Check if interval contains point."""
|
58
|
+
return self.low <= point <= self.high
|
59
|
+
|
60
|
+
def contains_interval(self, other: 'Interval') -> bool:
|
61
|
+
"""Check if this interval contains another."""
|
62
|
+
return self.low <= other.low and other.high <= self.high
|
63
|
+
|
64
|
+
def __eq__(self, other: Any) -> bool:
|
65
|
+
"""Equality comparison."""
|
66
|
+
if not isinstance(other, Interval):
|
67
|
+
return False
|
68
|
+
return self.low == other.low and self.high == other.high
|
69
|
+
|
70
|
+
def __lt__(self, other: 'Interval') -> bool:
|
71
|
+
"""Less than comparison (by low value)."""
|
72
|
+
return self.low < other.low or (self.low == other.low and self.high < other.high)
|
73
|
+
|
74
|
+
def __repr__(self) -> str:
|
75
|
+
"""String representation."""
|
76
|
+
return f"Interval([{self.low}, {self.high}])"
|
77
|
+
|
78
|
+
|
79
|
+
class IntervalNode:
|
80
|
+
"""
|
81
|
+
Node in interval tree.
|
82
|
+
|
83
|
+
WHY augmented node:
|
84
|
+
- Stores max endpoint in subtree for pruning
|
85
|
+
- Enables efficient overlap detection
|
86
|
+
- Red-black tree properties for balance
|
87
|
+
"""
|
88
|
+
|
89
|
+
def __init__(self, interval: Interval):
|
90
|
+
"""Initialize interval node."""
|
91
|
+
self.interval = interval
|
92
|
+
self.max_endpoint = interval.high # Augmented data
|
93
|
+
self.left: Optional['IntervalNode'] = None
|
94
|
+
self.right: Optional['IntervalNode'] = None
|
95
|
+
self.parent: Optional['IntervalNode'] = None
|
96
|
+
self.color = 'R' # Red-Black tree color
|
97
|
+
|
98
|
+
def update_max(self) -> None:
|
99
|
+
"""
|
100
|
+
Update max endpoint based on children.
|
101
|
+
|
102
|
+
WHY augmented max:
|
103
|
+
- Enables subtree pruning during overlap queries
|
104
|
+
- O(log n + k) query time where k is result size
|
105
|
+
"""
|
106
|
+
self.max_endpoint = self.interval.high
|
107
|
+
if self.left and self.left.max_endpoint > self.max_endpoint:
|
108
|
+
self.max_endpoint = self.left.max_endpoint
|
109
|
+
if self.right and self.right.max_endpoint > self.max_endpoint:
|
110
|
+
self.max_endpoint = self.right.max_endpoint
|
111
|
+
|
112
|
+
|
113
|
+
class IntervalTreeStrategy(ANodeTreeStrategy):
|
114
|
+
"""
|
115
|
+
Interval Tree strategy for efficient interval overlap queries.
|
116
|
+
|
117
|
+
WHY Interval Tree:
|
118
|
+
- O(log n + k) overlap queries where k is result size
|
119
|
+
- Essential for scheduling, genomics, collision detection
|
120
|
+
- Augmented balanced tree provides efficient pruning
|
121
|
+
- Handles dynamic interval insertions/deletions
|
122
|
+
- Supports both point and interval queries
|
123
|
+
|
124
|
+
WHY this implementation:
|
125
|
+
- Red-Black tree backbone ensures O(log n) height
|
126
|
+
- Augmented max values enable subtree pruning
|
127
|
+
- Sorted by interval start for efficient traversal
|
128
|
+
- Supports closed, open, and half-open intervals
|
129
|
+
- Value storage enables interval-keyed dictionaries
|
130
|
+
|
131
|
+
Time Complexity:
|
132
|
+
- Insert: O(log n)
|
133
|
+
- Delete: O(log n)
|
134
|
+
- Find overlaps: O(log n + k) where k is result size
|
135
|
+
- Find containing: O(log n + k)
|
136
|
+
- Point query: O(log n + k)
|
137
|
+
|
138
|
+
Space Complexity: O(n) for n intervals
|
139
|
+
|
140
|
+
Trade-offs:
|
141
|
+
- Advantage: Efficient overlap queries O(log n + k)
|
142
|
+
- Advantage: Handles dynamic intervals (insert/delete)
|
143
|
+
- Advantage: Better than O(n) scan for overlaps
|
144
|
+
- Limitation: Construction time O(n log n)
|
145
|
+
- Limitation: More complex than simple interval list
|
146
|
+
- Limitation: Requires balancing for optimal performance
|
147
|
+
- Compared to Segment Tree: More flexible intervals, overlap queries
|
148
|
+
- Compared to R-Tree: 1D intervals only, simpler structure
|
149
|
+
|
150
|
+
Best for:
|
151
|
+
- Scheduling systems (meeting conflicts, resource allocation)
|
152
|
+
- Genomics (gene overlaps, sequence alignment)
|
153
|
+
- Time-series (temporal interval queries)
|
154
|
+
- Collision detection (1D sweep)
|
155
|
+
- Range-based caching
|
156
|
+
- Event processing with time windows
|
157
|
+
|
158
|
+
Not recommended for:
|
159
|
+
- Point data (use BST instead)
|
160
|
+
- Multi-dimensional intervals (use R-tree)
|
161
|
+
- Static interval sets (use sorted array)
|
162
|
+
- Exact match queries only (use hash map)
|
163
|
+
- Very large result sets (k >> n)
|
164
|
+
|
165
|
+
Following eXonware Priorities:
|
166
|
+
1. Security: Validates interval bounds, prevents invalid ranges
|
167
|
+
2. Usability: Natural interval API, clear overlap semantics
|
168
|
+
3. Maintainability: Clean augmented tree structure
|
169
|
+
4. Performance: O(log n + k) queries, balanced tree
|
170
|
+
5. Extensibility: Easy to add stabbing queries, interval types
|
171
|
+
|
172
|
+
Industry Best Practices:
|
173
|
+
- Uses augmented Red-Black tree (Cormen et al.)
|
174
|
+
- Implements interval overlap as primary operation
|
175
|
+
- Supports both static and dynamic interval sets
|
176
|
+
- Provides point containment and interval containment
|
177
|
+
- Compatible with sweep-line algorithms
|
178
|
+
"""
|
179
|
+
|
180
|
+
# Tree node type for classification
|
181
|
+
STRATEGY_TYPE: NodeType = NodeType.TREE
|
182
|
+
|
183
|
+
def __init__(self, mode: NodeMode = NodeMode.INTERVAL_TREE,
|
184
|
+
traits: NodeTrait = NodeTrait.NONE, **options):
|
185
|
+
"""
|
186
|
+
Initialize interval tree strategy.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
mode: Node mode
|
190
|
+
traits: Node traits
|
191
|
+
**options: Additional options
|
192
|
+
"""
|
193
|
+
super().__init__(mode, traits, **options)
|
194
|
+
|
195
|
+
self._root: Optional[IntervalNode] = None
|
196
|
+
self._size = 0
|
197
|
+
self._intervals: Dict[Any, Interval] = {} # Key -> Interval mapping
|
198
|
+
|
199
|
+
def get_supported_traits(self) -> NodeTrait:
|
200
|
+
"""Get supported traits."""
|
201
|
+
return NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.HIERARCHICAL
|
202
|
+
|
203
|
+
# ============================================================================
|
204
|
+
# RED-BLACK TREE OPERATIONS (for balancing)
|
205
|
+
# ============================================================================
|
206
|
+
|
207
|
+
def _rotate_left(self, node: IntervalNode) -> None:
|
208
|
+
"""Rotate node left."""
|
209
|
+
right_child = node.right
|
210
|
+
node.right = right_child.left
|
211
|
+
|
212
|
+
if right_child.left:
|
213
|
+
right_child.left.parent = node
|
214
|
+
|
215
|
+
right_child.parent = node.parent
|
216
|
+
|
217
|
+
if node.parent is None:
|
218
|
+
self._root = right_child
|
219
|
+
elif node == node.parent.left:
|
220
|
+
node.parent.left = right_child
|
221
|
+
else:
|
222
|
+
node.parent.right = right_child
|
223
|
+
|
224
|
+
right_child.left = node
|
225
|
+
node.parent = right_child
|
226
|
+
|
227
|
+
# Update augmented max values
|
228
|
+
node.update_max()
|
229
|
+
right_child.update_max()
|
230
|
+
|
231
|
+
def _rotate_right(self, node: IntervalNode) -> None:
|
232
|
+
"""Rotate node right."""
|
233
|
+
left_child = node.left
|
234
|
+
node.left = left_child.right
|
235
|
+
|
236
|
+
if left_child.right:
|
237
|
+
left_child.right.parent = node
|
238
|
+
|
239
|
+
left_child.parent = node.parent
|
240
|
+
|
241
|
+
if node.parent is None:
|
242
|
+
self._root = left_child
|
243
|
+
elif node == node.parent.right:
|
244
|
+
node.parent.right = left_child
|
245
|
+
else:
|
246
|
+
node.parent.left = left_child
|
247
|
+
|
248
|
+
left_child.right = node
|
249
|
+
node.parent = left_child
|
250
|
+
|
251
|
+
# Update augmented max values
|
252
|
+
node.update_max()
|
253
|
+
left_child.update_max()
|
254
|
+
|
255
|
+
def _fix_insert(self, node: IntervalNode) -> None:
|
256
|
+
"""Fix Red-Black tree properties after insertion."""
|
257
|
+
while node.parent and node.parent.color == 'R':
|
258
|
+
if node.parent == node.parent.parent.left:
|
259
|
+
uncle = node.parent.parent.right
|
260
|
+
if uncle and uncle.color == 'R':
|
261
|
+
node.parent.color = 'B'
|
262
|
+
uncle.color = 'B'
|
263
|
+
node.parent.parent.color = 'R'
|
264
|
+
node = node.parent.parent
|
265
|
+
else:
|
266
|
+
if node == node.parent.right:
|
267
|
+
node = node.parent
|
268
|
+
self._rotate_left(node)
|
269
|
+
node.parent.color = 'B'
|
270
|
+
node.parent.parent.color = 'R'
|
271
|
+
self._rotate_right(node.parent.parent)
|
272
|
+
else:
|
273
|
+
uncle = node.parent.parent.left
|
274
|
+
if uncle and uncle.color == 'R':
|
275
|
+
node.parent.color = 'B'
|
276
|
+
uncle.color = 'B'
|
277
|
+
node.parent.parent.color = 'R'
|
278
|
+
node = node.parent.parent
|
279
|
+
else:
|
280
|
+
if node == node.parent.left:
|
281
|
+
node = node.parent
|
282
|
+
self._rotate_right(node)
|
283
|
+
node.parent.color = 'B'
|
284
|
+
node.parent.parent.color = 'R'
|
285
|
+
self._rotate_left(node.parent.parent)
|
286
|
+
|
287
|
+
if self._root:
|
288
|
+
self._root.color = 'B'
|
289
|
+
|
290
|
+
# ============================================================================
|
291
|
+
# CORE INTERVAL OPERATIONS
|
292
|
+
# ============================================================================
|
293
|
+
|
294
|
+
def put(self, key: Any, value: Any = None) -> None:
|
295
|
+
"""
|
296
|
+
Insert interval.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
key: Interval object or tuple (low, high)
|
300
|
+
value: Associated value
|
301
|
+
|
302
|
+
Raises:
|
303
|
+
XWNodeValueError: If key is invalid interval
|
304
|
+
"""
|
305
|
+
# Security: Parse interval
|
306
|
+
if isinstance(key, Interval):
|
307
|
+
interval = key
|
308
|
+
if value is not None:
|
309
|
+
interval.value = value
|
310
|
+
elif isinstance(key, (tuple, list)) and len(key) == 2:
|
311
|
+
interval = Interval(key[0], key[1], value)
|
312
|
+
else:
|
313
|
+
raise XWNodeValueError(
|
314
|
+
f"Key must be Interval or (low, high) tuple, got {type(key).__name__}"
|
315
|
+
)
|
316
|
+
|
317
|
+
# Create new node
|
318
|
+
new_node = IntervalNode(interval)
|
319
|
+
|
320
|
+
# Insert into tree
|
321
|
+
if self._root is None:
|
322
|
+
self._root = new_node
|
323
|
+
self._root.color = 'B'
|
324
|
+
else:
|
325
|
+
parent = None
|
326
|
+
current = self._root
|
327
|
+
|
328
|
+
# Find insertion position
|
329
|
+
while current:
|
330
|
+
parent = current
|
331
|
+
if interval < current.interval:
|
332
|
+
current = current.left
|
333
|
+
else:
|
334
|
+
current = current.right
|
335
|
+
|
336
|
+
# Link new node
|
337
|
+
new_node.parent = parent
|
338
|
+
if interval < parent.interval:
|
339
|
+
parent.left = new_node
|
340
|
+
else:
|
341
|
+
parent.right = new_node
|
342
|
+
|
343
|
+
# Fix Red-Black properties
|
344
|
+
self._fix_insert(new_node)
|
345
|
+
|
346
|
+
# Update augmented max values up the tree
|
347
|
+
current = new_node.parent
|
348
|
+
while current:
|
349
|
+
current.update_max()
|
350
|
+
current = current.parent
|
351
|
+
|
352
|
+
# Store interval for key-based access
|
353
|
+
self._intervals[id(interval)] = interval
|
354
|
+
self._size += 1
|
355
|
+
|
356
|
+
def get(self, key: Any, default: Any = None) -> Any:
|
357
|
+
"""
|
358
|
+
Get value for exact interval match.
|
359
|
+
|
360
|
+
Args:
|
361
|
+
key: Interval or tuple
|
362
|
+
default: Default value
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
Value or default
|
366
|
+
"""
|
367
|
+
if isinstance(key, Interval):
|
368
|
+
search_interval = key
|
369
|
+
elif isinstance(key, (tuple, list)) and len(key) == 2:
|
370
|
+
search_interval = Interval(key[0], key[1])
|
371
|
+
else:
|
372
|
+
return default
|
373
|
+
|
374
|
+
node = self._find_exact(self._root, search_interval)
|
375
|
+
return node.interval.value if node else default
|
376
|
+
|
377
|
+
def _find_exact(self, node: Optional[IntervalNode], interval: Interval) -> Optional[IntervalNode]:
|
378
|
+
"""Find node with exact interval match."""
|
379
|
+
if node is None:
|
380
|
+
return None
|
381
|
+
|
382
|
+
if interval == node.interval:
|
383
|
+
return node
|
384
|
+
elif interval < node.interval:
|
385
|
+
return self._find_exact(node.left, interval)
|
386
|
+
else:
|
387
|
+
return self._find_exact(node.right, interval)
|
388
|
+
|
389
|
+
def has(self, key: Any) -> bool:
|
390
|
+
"""Check if exact interval exists."""
|
391
|
+
return self.get(key) is not None
|
392
|
+
|
393
|
+
def delete(self, key: Any) -> bool:
|
394
|
+
"""
|
395
|
+
Delete interval.
|
396
|
+
|
397
|
+
Args:
|
398
|
+
key: Interval or tuple
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
True if deleted, False if not found
|
402
|
+
|
403
|
+
Note: Simplified deletion. Full RB-tree deletion is complex.
|
404
|
+
"""
|
405
|
+
if isinstance(key, Interval):
|
406
|
+
search_interval = key
|
407
|
+
elif isinstance(key, (tuple, list)) and len(key) == 2:
|
408
|
+
search_interval = Interval(key[0], key[1])
|
409
|
+
else:
|
410
|
+
return False
|
411
|
+
|
412
|
+
node = self._find_exact(self._root, search_interval)
|
413
|
+
if not node:
|
414
|
+
return False
|
415
|
+
|
416
|
+
# Remove from intervals dict
|
417
|
+
if id(node.interval) in self._intervals:
|
418
|
+
del self._intervals[id(node.interval)]
|
419
|
+
|
420
|
+
self._size -= 1
|
421
|
+
|
422
|
+
# Simplified deletion (doesn't rebalance)
|
423
|
+
# Full implementation would do RB-tree deletion with fixup
|
424
|
+
if node.left is None and node.right is None:
|
425
|
+
if node.parent:
|
426
|
+
if node == node.parent.left:
|
427
|
+
node.parent.left = None
|
428
|
+
else:
|
429
|
+
node.parent.right = None
|
430
|
+
|
431
|
+
# Update max values
|
432
|
+
current = node.parent
|
433
|
+
while current:
|
434
|
+
current.update_max()
|
435
|
+
current = current.parent
|
436
|
+
else:
|
437
|
+
self._root = None
|
438
|
+
|
439
|
+
return True
|
440
|
+
|
441
|
+
# ============================================================================
|
442
|
+
# INTERVAL QUERY OPERATIONS
|
443
|
+
# ============================================================================
|
444
|
+
|
445
|
+
def find_overlaps(self, query: Tuple[float, float]) -> List[Interval]:
|
446
|
+
"""
|
447
|
+
Find all intervals that overlap with query interval.
|
448
|
+
|
449
|
+
Args:
|
450
|
+
query: Tuple (low, high) or Interval
|
451
|
+
|
452
|
+
Returns:
|
453
|
+
List of overlapping intervals
|
454
|
+
|
455
|
+
Raises:
|
456
|
+
XWNodeValueError: If query is invalid
|
457
|
+
|
458
|
+
WHY O(log n + k) complexity:
|
459
|
+
- Augmented max enables subtree pruning
|
460
|
+
- Only explores relevant subtrees
|
461
|
+
- Optimal for sparse overlaps
|
462
|
+
"""
|
463
|
+
if isinstance(query, Interval):
|
464
|
+
query_interval = query
|
465
|
+
elif isinstance(query, (tuple, list)) and len(query) == 2:
|
466
|
+
query_interval = Interval(query[0], query[1])
|
467
|
+
else:
|
468
|
+
raise XWNodeValueError(
|
469
|
+
f"Query must be Interval or (low, high) tuple"
|
470
|
+
)
|
471
|
+
|
472
|
+
result = []
|
473
|
+
self._search_overlaps(self._root, query_interval, result)
|
474
|
+
return result
|
475
|
+
|
476
|
+
def _search_overlaps(self, node: Optional[IntervalNode],
|
477
|
+
query: Interval, result: List[Interval]) -> None:
|
478
|
+
"""
|
479
|
+
Recursively search for overlapping intervals.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
node: Current node
|
483
|
+
query: Query interval
|
484
|
+
result: Accumulator list
|
485
|
+
|
486
|
+
WHY recursive search:
|
487
|
+
- Explores all relevant subtrees
|
488
|
+
- Prunes using augmented max values
|
489
|
+
- Efficient for sparse result sets
|
490
|
+
"""
|
491
|
+
if node is None:
|
492
|
+
return
|
493
|
+
|
494
|
+
# Check overlap with current interval
|
495
|
+
if node.interval.overlaps(query):
|
496
|
+
result.append(node.interval)
|
497
|
+
|
498
|
+
# Search left if left subtree might have overlaps
|
499
|
+
if node.left and node.left.max_endpoint >= query.low:
|
500
|
+
self._search_overlaps(node.left, query, result)
|
501
|
+
|
502
|
+
# Search right if needed
|
503
|
+
if node.right and node.interval.low <= query.high:
|
504
|
+
self._search_overlaps(node.right, query, result)
|
505
|
+
|
506
|
+
def find_containing_point(self, point: float) -> List[Interval]:
|
507
|
+
"""
|
508
|
+
Find all intervals containing a point.
|
509
|
+
|
510
|
+
Args:
|
511
|
+
point: Query point
|
512
|
+
|
513
|
+
Returns:
|
514
|
+
List of intervals containing the point
|
515
|
+
"""
|
516
|
+
query = Interval(point, point)
|
517
|
+
return self.find_overlaps(query)
|
518
|
+
|
519
|
+
def find_contained_in(self, interval: Tuple[float, float]) -> List[Interval]:
|
520
|
+
"""
|
521
|
+
Find all intervals contained within query interval.
|
522
|
+
|
523
|
+
Args:
|
524
|
+
interval: Query interval (low, high)
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
List of intervals contained in query interval
|
528
|
+
"""
|
529
|
+
if isinstance(interval, (tuple, list)) and len(interval) == 2:
|
530
|
+
query_interval = Interval(interval[0], interval[1])
|
531
|
+
else:
|
532
|
+
raise XWNodeValueError("Interval must be (low, high) tuple")
|
533
|
+
|
534
|
+
result = []
|
535
|
+
self._search_contained(self._root, query_interval, result)
|
536
|
+
return result
|
537
|
+
|
538
|
+
def _search_contained(self, node: Optional[IntervalNode],
|
539
|
+
query: Interval, result: List[Interval]) -> None:
|
540
|
+
"""Search for intervals contained in query."""
|
541
|
+
if node is None:
|
542
|
+
return
|
543
|
+
|
544
|
+
# Check if node interval is contained
|
545
|
+
if query.contains_interval(node.interval):
|
546
|
+
result.append(node.interval)
|
547
|
+
|
548
|
+
# Search both subtrees if they might have contained intervals
|
549
|
+
if node.left and node.left.max_endpoint >= query.low:
|
550
|
+
self._search_contained(node.left, query, result)
|
551
|
+
|
552
|
+
if node.right and node.interval.low <= query.high:
|
553
|
+
self._search_contained(node.right, query, result)
|
554
|
+
|
555
|
+
# ============================================================================
|
556
|
+
# STANDARD OPERATIONS
|
557
|
+
# ============================================================================
|
558
|
+
|
559
|
+
def keys(self) -> Iterator[Any]:
|
560
|
+
"""Get iterator over all intervals."""
|
561
|
+
yield from self._inorder_traversal(self._root)
|
562
|
+
|
563
|
+
def _inorder_traversal(self, node: Optional[IntervalNode]) -> Iterator[Interval]:
|
564
|
+
"""Inorder traversal of intervals."""
|
565
|
+
if node is None:
|
566
|
+
return
|
567
|
+
|
568
|
+
yield from self._inorder_traversal(node.left)
|
569
|
+
yield node.interval
|
570
|
+
yield from self._inorder_traversal(node.right)
|
571
|
+
|
572
|
+
def values(self) -> Iterator[Any]:
|
573
|
+
"""Get iterator over all values."""
|
574
|
+
for interval in self.keys():
|
575
|
+
yield interval.value
|
576
|
+
|
577
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
578
|
+
"""Get iterator over interval-value pairs."""
|
579
|
+
for interval in self.keys():
|
580
|
+
yield (interval, interval.value)
|
581
|
+
|
582
|
+
def __len__(self) -> int:
|
583
|
+
"""Get number of intervals."""
|
584
|
+
return self._size
|
585
|
+
|
586
|
+
def to_native(self) -> Any:
|
587
|
+
"""
|
588
|
+
Convert to native list of intervals.
|
589
|
+
|
590
|
+
Returns:
|
591
|
+
List of interval dictionaries
|
592
|
+
"""
|
593
|
+
return [
|
594
|
+
{
|
595
|
+
'low': interval.low,
|
596
|
+
'high': interval.high,
|
597
|
+
'value': interval.value
|
598
|
+
}
|
599
|
+
for interval in self.keys()
|
600
|
+
]
|
601
|
+
|
602
|
+
# ============================================================================
|
603
|
+
# UTILITY METHODS
|
604
|
+
# ============================================================================
|
605
|
+
|
606
|
+
def clear(self) -> None:
|
607
|
+
"""Clear all intervals."""
|
608
|
+
self._root = None
|
609
|
+
self._size = 0
|
610
|
+
self._intervals.clear()
|
611
|
+
|
612
|
+
def is_empty(self) -> bool:
|
613
|
+
"""Check if empty."""
|
614
|
+
return self._size == 0
|
615
|
+
|
616
|
+
def size(self) -> int:
|
617
|
+
"""Get number of intervals."""
|
618
|
+
return self._size
|
619
|
+
|
620
|
+
def get_mode(self) -> NodeMode:
|
621
|
+
"""Get strategy mode."""
|
622
|
+
return self.mode
|
623
|
+
|
624
|
+
def get_traits(self) -> NodeTrait:
|
625
|
+
"""Get strategy traits."""
|
626
|
+
return self.traits
|
627
|
+
|
628
|
+
def get_height(self) -> int:
|
629
|
+
"""Get tree height."""
|
630
|
+
return self._get_height(self._root)
|
631
|
+
|
632
|
+
def _get_height(self, node: Optional[IntervalNode]) -> int:
|
633
|
+
"""Recursively calculate height."""
|
634
|
+
if node is None:
|
635
|
+
return 0
|
636
|
+
return 1 + max(self._get_height(node.left), self._get_height(node.right))
|
637
|
+
|
638
|
+
# ============================================================================
|
639
|
+
# STATISTICS
|
640
|
+
# ============================================================================
|
641
|
+
|
642
|
+
def get_statistics(self) -> Dict[str, Any]:
|
643
|
+
"""
|
644
|
+
Get tree statistics.
|
645
|
+
|
646
|
+
Returns:
|
647
|
+
Statistics dictionary
|
648
|
+
"""
|
649
|
+
intervals_list = list(self.keys())
|
650
|
+
|
651
|
+
if not intervals_list:
|
652
|
+
return {
|
653
|
+
'size': 0,
|
654
|
+
'height': 0,
|
655
|
+
'max_overlap': 0,
|
656
|
+
'avg_interval_length': 0
|
657
|
+
}
|
658
|
+
|
659
|
+
# Calculate max overlap at any point
|
660
|
+
points = []
|
661
|
+
for interval in intervals_list:
|
662
|
+
points.append((interval.low, 1)) # Start
|
663
|
+
points.append((interval.high, -1)) # End
|
664
|
+
|
665
|
+
points.sort()
|
666
|
+
max_overlap = 0
|
667
|
+
current_overlap = 0
|
668
|
+
for point, delta in points:
|
669
|
+
current_overlap += delta
|
670
|
+
max_overlap = max(max_overlap, current_overlap)
|
671
|
+
|
672
|
+
# Calculate average interval length
|
673
|
+
avg_length = sum(i.high - i.low for i in intervals_list) / len(intervals_list)
|
674
|
+
|
675
|
+
return {
|
676
|
+
'size': self._size,
|
677
|
+
'height': self.get_height(),
|
678
|
+
'max_overlap': max_overlap,
|
679
|
+
'avg_interval_length': avg_length,
|
680
|
+
'min_endpoint': min(i.low for i in intervals_list),
|
681
|
+
'max_endpoint': max(i.high for i in intervals_list)
|
682
|
+
}
|
683
|
+
|
684
|
+
# ============================================================================
|
685
|
+
# COMPATIBILITY METHODS
|
686
|
+
# ============================================================================
|
687
|
+
|
688
|
+
def find(self, key: Any) -> Optional[Any]:
|
689
|
+
"""Find value by interval."""
|
690
|
+
return self.get(key)
|
691
|
+
|
692
|
+
def insert(self, key: Any, value: Any = None) -> None:
|
693
|
+
"""Insert interval."""
|
694
|
+
self.put(key, value)
|
695
|
+
|
696
|
+
def __str__(self) -> str:
|
697
|
+
"""String representation."""
|
698
|
+
return f"IntervalTreeStrategy(size={self._size}, height={self.get_height()})"
|
699
|
+
|
700
|
+
def __repr__(self) -> str:
|
701
|
+
"""Detailed representation."""
|
702
|
+
return f"IntervalTreeStrategy(mode={self.mode.name}, size={self._size}, traits={self.traits})"
|
703
|
+
|
704
|
+
# ============================================================================
|
705
|
+
# FACTORY METHOD
|
706
|
+
# ============================================================================
|
707
|
+
|
708
|
+
@classmethod
|
709
|
+
def create_from_data(cls, data: Any) -> 'IntervalTreeStrategy':
|
710
|
+
"""
|
711
|
+
Create interval tree from data.
|
712
|
+
|
713
|
+
Args:
|
714
|
+
data: Dict of intervals or list of tuples
|
715
|
+
|
716
|
+
Returns:
|
717
|
+
New IntervalTreeStrategy instance
|
718
|
+
"""
|
719
|
+
instance = cls()
|
720
|
+
|
721
|
+
if isinstance(data, dict):
|
722
|
+
for key, value in data.items():
|
723
|
+
if isinstance(key, (tuple, list)) and len(key) == 2:
|
724
|
+
instance.put(key, value)
|
725
|
+
else:
|
726
|
+
raise XWNodeValueError(
|
727
|
+
f"Interval tree requires (low, high) tuple keys"
|
728
|
+
)
|
729
|
+
elif isinstance(data, (list, tuple)):
|
730
|
+
for item in data:
|
731
|
+
if isinstance(item, (tuple, list)) and len(item) >= 2:
|
732
|
+
if len(item) == 3:
|
733
|
+
instance.put((item[0], item[1]), item[2])
|
734
|
+
else:
|
735
|
+
instance.put((item[0], item[1]), None)
|
736
|
+
else:
|
737
|
+
raise XWNodeValueError(
|
738
|
+
"Data must be dict with interval keys or list of interval tuples"
|
739
|
+
)
|
740
|
+
|
741
|
+
return instance
|
742
|
+
|