treemendous 0.1.0__tar.gz

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.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, Joseph Cox
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: treemendous
3
+ Version: 0.1.0
4
+ Summary: Exploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates.
5
+ License: BSD 3-Clause License
6
+ Author: Joseph Cox
7
+ Author-email: joseph@codensity.io
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Requires-Dist: sortedcollections (>=2.1.0,<3.0.0)
17
+ Requires-Dist: sortedcontainers (>=2.4.0,<3.0.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Tree-Mendous
21
+ Exploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates.
22
+
@@ -0,0 +1,2 @@
1
+ # Tree-Mendous
2
+ Exploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates.
@@ -0,0 +1,55 @@
1
+ import os
2
+ import shutil
3
+ from typing import List, Dict, Any
4
+ from pathlib import Path
5
+
6
+ from pybind11.setup_helpers import Pybind11Extension
7
+ from setuptools.command.build_ext import build_ext
8
+ from setuptools.dist import Distribution
9
+
10
+ def build(setup_kwargs: Dict[str, Any], with_ic: bool = False) -> None:
11
+ compile_args = ["-O3"]
12
+ include_dirs = []
13
+ libraries = []
14
+ extra_link_args = []
15
+
16
+ if with_ic:
17
+ compile_args.append("-DWITH_IC_MANAGER")
18
+ include_dirs.append("/opt/homebrew/Cellar/boost/1.86.0_2/include")
19
+ libraries.append("boost_system")
20
+ extra_link_args.append("-L/opt/homebrew/Cellar/boost/1.86.0_2/lib")
21
+
22
+ ext_modules: List[Pybind11Extension] = [
23
+ Pybind11Extension(
24
+ "treemendous.cpp.boundary",
25
+ ["treemendous/cpp/boundary_bindings.cpp"],
26
+ cxx_std=20,
27
+ extra_compile_args=compile_args,
28
+ include_dirs=include_dirs,
29
+ libraries=libraries,
30
+ extra_link_args=extra_link_args,
31
+ ),
32
+ ]
33
+
34
+ distribution = Distribution({
35
+ "name": "treemendous",
36
+ "ext_modules": ext_modules
37
+ })
38
+
39
+ cmd = build_ext(distribution)
40
+ cmd.ensure_finalized()
41
+ cmd.run()
42
+
43
+ # Copy built extensions back to the project
44
+ for output in cmd.get_outputs():
45
+ output = Path(output)
46
+ relative_extension = output.relative_to(cmd.build_lib)
47
+
48
+ shutil.copyfile(output, relative_extension)
49
+ mode = os.stat(relative_extension).st_mode
50
+ mode |= (mode & 0o444) >> 2
51
+ os.chmod(relative_extension, mode)
52
+ # setup_kwargs.update({
53
+ # "ext_modules": ext_modules,
54
+ # "cmdclass": {"build_ext": build_ext},
55
+ # })
@@ -0,0 +1,35 @@
1
+ [tool.poetry]
2
+ name = "treemendous"
3
+ version = "0.1.0"
4
+ description = "Exploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates."
5
+ authors = ["Joseph Cox <joseph@codensity.io>"]
6
+ license = "BSD 3-Clause License"
7
+ readme = "README.md"
8
+
9
+
10
+
11
+ [tool.poetry.dependencies]
12
+ python = "^3.9"
13
+ sortedcollections = "^2.1.0"
14
+ sortedcontainers = "^2.4.0"
15
+
16
+ [tool.poetry.group.dev.dependencies]
17
+ sortedcontainers-stubs = "^2.4.2"
18
+ flameprof = "^0.4"
19
+
20
+
21
+ [tool.poetry.group.test.dependencies]
22
+ hypothesis = "^6.119.4"
23
+ pytest = "^8.3.3"
24
+
25
+ [tool.pytest.ini_options]
26
+ addopts = "--hypothesis-explain -vv"
27
+
28
+
29
+ [build-system]
30
+ requires = ["poetry-core>=1.0.0", "pybind11>=2.13.6", "setuptools>=75.6.0"]
31
+ # build-backend = "poetry.core.masonry.api"
32
+
33
+ [tool.poetry.build]
34
+ script = "build.py"
35
+ generate-setup-file = true
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ from setuptools import setup
3
+
4
+ packages = \
5
+ ['treemendous', 'treemendous.basic', 'treemendous.cpp']
6
+
7
+ package_data = \
8
+ {'': ['*']}
9
+
10
+ install_requires = \
11
+ ['sortedcollections>=2.1.0,<3.0.0', 'sortedcontainers>=2.4.0,<3.0.0']
12
+
13
+ setup_kwargs = {
14
+ 'name': 'treemendous',
15
+ 'version': '0.1.0',
16
+ 'description': 'Exploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates.',
17
+ 'long_description': '# Tree-Mendous\nExploring a diverse collection of interval tree implementations across multiple programming languages to identify the most efficient configurations for varied use cases, such as optimizing for query speed, memory usage, or dynamic updates.\n',
18
+ 'author': 'Joseph Cox',
19
+ 'author_email': 'joseph@codensity.io',
20
+ 'maintainer': 'None',
21
+ 'maintainer_email': 'None',
22
+ 'url': 'None',
23
+ 'packages': packages,
24
+ 'package_data': package_data,
25
+ 'install_requires': install_requires,
26
+ 'python_requires': '>=3.9,<4.0',
27
+ }
28
+ from build import *
29
+ build(setup_kwargs)
30
+
31
+ setup(**setup_kwargs)
File without changes
File without changes
@@ -0,0 +1,283 @@
1
+ from typing import Generic, Optional, List, Tuple, TypeVar, cast, overload
2
+ from treemendous.basic.base import IntervalNodeBase, IntervalNodeProtocol, IntervalTreeBase
3
+
4
+
5
+
6
+ class IntervalNode(IntervalNodeBase[IntervalNodeProtocol]):
7
+ def __init__(self, start: int, end: int) -> None:
8
+ super().__init__(start, end)
9
+ self.total_length: int = self.length
10
+ self.height: int = 1
11
+
12
+ def update_stats(self) -> None:
13
+ self.update_length()
14
+ self.total_length = self.length
15
+ if self.left:
16
+ self.total_length += self.left.total_length
17
+ if self.right:
18
+ self.total_length += self.right.total_length
19
+ self.height = 1 + max(
20
+ self.get_height(self.left),
21
+ self.get_height(self.right)
22
+ )
23
+
24
+ @staticmethod
25
+ def get_height(node: Optional['IntervalNode']) -> int:
26
+ return node.height if node else 0
27
+
28
+ R = TypeVar('R', bound=IntervalNode)
29
+ class IntervalTree(Generic[R], IntervalTreeBase[R]):
30
+ def __init__(self, node_class: type[R]) -> None:
31
+ super().__init__()
32
+ self.node_class = node_class
33
+ self.root: Optional[R] = None
34
+
35
+ def _print_node(self, node: R, indent: str, prefix: str) -> None:
36
+ print(f"{indent}{prefix}{node.start}-{node.end} (len={node.length}, total_len={node.total_length})")
37
+
38
+ def release_interval(self, start: int, end: int) -> None:
39
+ overlapping_nodes: List[R] = []
40
+ self.root = self._delete_overlaps(self.root, start, end, overlapping_nodes)
41
+ # Merge overlapping intervals with the new interval
42
+ for node in overlapping_nodes:
43
+ start = min(start, node.start)
44
+ end = max(end, node.end)
45
+ # Insert the merged interval using the constructor
46
+ self.root = self._insert(self.root, self.node_class(start, end))
47
+
48
+ def reserve_interval(self, start: int, end: int) -> None:
49
+ self.root = self._delete_interval(self.root, start, end)
50
+
51
+ def _delete_interval(
52
+ self, node: Optional[R], start: int, end: int
53
+ ) -> Optional[R]:
54
+ if not node:
55
+ return None
56
+
57
+ if node.end <= start:
58
+ # Interval to delete is after the current node
59
+ node.right = self._delete_interval(node.right, start, end)
60
+ elif node.start >= end:
61
+ # Interval to delete is before the current node
62
+ node.left = self._delete_interval(node.left, start, end)
63
+ else:
64
+ # The current node overlaps with the interval to delete
65
+ # We may need to split the node into up to two intervals
66
+
67
+ nodes_to_insert = []
68
+
69
+ if node.start < start:
70
+ # Left part remains
71
+ left_node = self.node_class(node.start, start)
72
+ nodes_to_insert.append(left_node)
73
+
74
+ if node.end > end:
75
+ # Right part remains
76
+ right_node = self.node_class(end, node.end)
77
+ nodes_to_insert.append(right_node)
78
+
79
+ # Delete the current node and replace it with left and right parts
80
+ node = self._merge_subtrees(
81
+ self._delete_interval(node.left, start, end),
82
+ self._delete_interval(node.right, start, end)
83
+ )
84
+
85
+ # Insert any remaining parts
86
+ for n in nodes_to_insert:
87
+ node = self._insert(node, n)
88
+
89
+ if node:
90
+ node.update_stats()
91
+ node = self._rebalance(node)
92
+ return node
93
+
94
+ def _delete_overlaps(
95
+ self, node: Optional[R], start: int, end: int, overlapping_nodes: List[R]
96
+ ) -> Optional[R]:
97
+ if not node:
98
+ return None
99
+
100
+ if node.end <= start:
101
+ # No overlap, move to the right
102
+ node.right = self._delete_overlaps(node.right, start, end, overlapping_nodes)
103
+ elif node.start >= end:
104
+ # No overlap, move to the left
105
+ node.left = self._delete_overlaps(node.left, start, end, overlapping_nodes)
106
+ else:
107
+ # Overlap detected
108
+ overlapping_nodes.append(node)
109
+ # Remove this node and continue searching in both subtrees
110
+ node = self._merge_subtrees(
111
+ self._delete_overlaps(node.left, start, end, overlapping_nodes),
112
+ self._delete_overlaps(node.right, start, end, overlapping_nodes)
113
+ )
114
+ return node
115
+
116
+ if node:
117
+ node.update_stats()
118
+ node = self._rebalance(node)
119
+ return node
120
+
121
+ def _merge_subtrees(
122
+ self, left: Optional[R], right: Optional[R]
123
+ ) -> Optional[R]:
124
+ if not left:
125
+ return right
126
+ if not right:
127
+ return left
128
+
129
+ # Find the node with the minimum start in the right subtree
130
+ min_node = self._get_min(right)
131
+ right = self._delete_min(right)
132
+ min_node.left = left
133
+ min_node.right = right
134
+ min_node.update_stats()
135
+ return self._rebalance(min_node)
136
+
137
+ def _delete_min(self, node: R) -> Optional[R]:
138
+ if node.left is None:
139
+ return node.right
140
+ node.left = self._delete_min(node.left)
141
+ node.update_stats()
142
+ return self._rebalance(node)
143
+
144
+ def _insert(self, node: Optional[R], new_node: R) -> R:
145
+ if not node:
146
+ return new_node
147
+
148
+ if new_node.start < node.start:
149
+ node.left = self._insert(node.left, new_node)
150
+ else:
151
+ node.right = self._insert(node.right, new_node)
152
+
153
+ node.update_stats()
154
+ node = self._rebalance(node)
155
+ return node
156
+
157
+ def _get_min(self, node: IntervalNode) -> IntervalNode:
158
+ current = node
159
+ while current.left:
160
+ current = current.left
161
+ return current
162
+
163
+ def _rebalance(self, node: R) -> R:
164
+ balance = self._get_balance(node)
165
+ if balance > 1:
166
+ # Left heavy
167
+ if self._get_balance(node.left) < 0:
168
+ # Left-Right case
169
+ node.left = self._rotate_left(node.left)
170
+ # Left-Left case
171
+ node = self._rotate_right(node)
172
+ elif balance < -1:
173
+ # Right heavy
174
+ if self._get_balance(node.right) > 0:
175
+ # Right-Left case
176
+ node.right = self._rotate_right(node.right)
177
+ # Right-Right case
178
+ node = self._rotate_left(node)
179
+ return node
180
+
181
+ def _get_balance(self, node: Optional[IntervalNode]) -> int:
182
+ if not node:
183
+ return 0
184
+ return IntervalNode.get_height(node.left) - IntervalNode.get_height(node.right)
185
+
186
+ @overload
187
+ def _rotate_left(self, z: None) -> None: ...
188
+
189
+ @overload
190
+ def _rotate_left(self, z: R) -> R: ...
191
+
192
+ def _rotate_left(self, z: Optional[R]) -> Optional[R]:
193
+ if not z or not z.right:
194
+ return z
195
+ y: R = z.right
196
+ subtree: Optional[R] = y.left
197
+
198
+ # Perform rotation
199
+ y.left = z
200
+ z.right = subtree
201
+
202
+ # Update heights and stats
203
+ z.update_stats()
204
+ y.update_stats()
205
+ return y
206
+
207
+ @overload
208
+ def _rotate_right(self, z: None) -> None: ...
209
+
210
+ @overload
211
+ def _rotate_right(self, z: R) -> R: ...
212
+
213
+ def _rotate_right(self, z: Optional[R]) -> Optional[R]:
214
+ if not z or not z.left:
215
+ return z
216
+ y: R = z.left
217
+ subtree: Optional[R] = y.right
218
+
219
+ # Perform rotation
220
+ y.right = z
221
+ z.left = subtree
222
+
223
+ # Update heights and stats
224
+ z.update_stats()
225
+ y.update_stats()
226
+ return y
227
+
228
+ def get_intervals(self) -> List[Tuple[int, int]]:
229
+ intervals: List[Tuple[int, int]] = []
230
+ self._get_intervals(self.root, intervals)
231
+ return intervals
232
+
233
+ def _get_intervals(self, node: Optional[R], intervals: List[Tuple[int, int]]) -> None:
234
+ if not node:
235
+ return
236
+ intervals.append((node.start, node.end))
237
+ self._get_intervals(node.left, intervals)
238
+ self._get_intervals(node.right, intervals)
239
+
240
+ # Example usage:
241
+ if __name__ == "__main__":
242
+ tree = IntervalTree[IntervalNode](IntervalNode)
243
+ # Initially, the whole interval [0, 100] is available
244
+ tree.release_interval(0, 100)
245
+ print("Initial tree:")
246
+ tree.print_tree()
247
+ print(f"Total available length: {tree.get_total_available_length()}")
248
+
249
+ # Schedule interval [0, 1
250
+ tree.reserve_interval(0, 1)
251
+ print("\nAfter scheduling [0, 1]:")
252
+ tree.print_tree()
253
+ print(f"Total available length: {tree.get_total_available_length()}")
254
+
255
+ # Unschedule interval [0, 1]
256
+ tree.release_interval(0, 1)
257
+ print("\nAfter unscheduling [0, 1]:")
258
+ tree.print_tree()
259
+ print(f"Total available length: {tree.get_total_available_length()}")
260
+
261
+ # Schedule interval [10, 20]
262
+ tree.reserve_interval(10, 20)
263
+ print("\nAfter scheduling [10, 20]:")
264
+ tree.print_tree()
265
+ print(f"Total available length: {tree.get_total_available_length()}")
266
+
267
+ # Schedule interval [30, 40]
268
+ tree.reserve_interval(30, 40)
269
+ print("\nAfter scheduling [30, 40]:")
270
+ tree.print_tree()
271
+ print(f"Total available length: {tree.get_total_available_length()}")
272
+
273
+ # Unschedule interval [10, 20]
274
+ tree.release_interval(10, 20)
275
+ print("\nAfter unscheduling [10, 20]:")
276
+ tree.print_tree()
277
+ print(f"Total available length: {tree.get_total_available_length()}")
278
+
279
+ # Split at pivot 50 (delete [50, 50])
280
+ tree.reserve_interval(50, 50)
281
+ print("\nAfter splitting at pivot 50:")
282
+ tree.print_tree()
283
+ print(f"Total available length: {tree.get_total_available_length()}")
@@ -0,0 +1,127 @@
1
+ from typing import Optional
2
+
3
+ from treemendous.basic.base import IntervalNodeProtocol
4
+ from treemendous.basic.avl import IntervalNode, IntervalTree
5
+
6
+ class EarliestIntervalNode(IntervalNode, IntervalNodeProtocol):
7
+ def __init__(self, start: int, end: int) -> None:
8
+ super().__init__(start, end)
9
+ self.min_start: int = start
10
+ self.max_end: int = end
11
+ self.max_length: int = end - start
12
+
13
+ def update_stats(self) -> None:
14
+ super().update_stats()
15
+
16
+ self.min_start = self.start
17
+ self.max_end = self.end
18
+ self.max_length = self.end - self.start
19
+
20
+ if self.left:
21
+ assert isinstance(self.left, EarliestIntervalNode)
22
+ self.min_start = min(self.min_start, self.left.min_start)
23
+ self.max_end = max(self.max_end, self.left.max_end)
24
+ self.max_length = max(self.max_length, self.left.max_length)
25
+ if self.right:
26
+ assert isinstance(self.right, EarliestIntervalNode)
27
+ self.min_start = min(self.min_start, self.right.min_start)
28
+ self.max_end = max(self.max_end, self.right.max_end)
29
+ self.max_length = max(self.max_length, self.right.max_length)
30
+
31
+ class EarliestIntervalTree(IntervalTree[EarliestIntervalNode]):
32
+ def __init__(self) -> None:
33
+ super().__init__(EarliestIntervalNode)
34
+
35
+ def _print_node(self, node: EarliestIntervalNode, indent: str, prefix: str) -> None:
36
+ print(f"{indent}{prefix}{node.start}-{node.end} "
37
+ f"(min_start={node.min_start}, max_end={node.max_end}, max_length={node.max_length})")
38
+
39
+ def find_interval(self, point: int, length: int) -> Optional[EarliestIntervalNode]:
40
+ return self._find_interval(self.root, point, length)
41
+
42
+ def _find_interval(self, node: Optional[EarliestIntervalNode], point: int,
43
+ length: int) -> Optional[EarliestIntervalNode]:
44
+ if not node:
45
+ return None
46
+ if node.start >= point and (node.end - node.start) >= length:
47
+ # Potential candidate
48
+ left_candidate = self._find_interval(node.left, point, length)
49
+ return left_candidate if left_candidate else node
50
+ elif node.start < point:
51
+ # Search right subtree
52
+ return self._find_interval(node.right, point, length)
53
+ else:
54
+ # Node's interval is too short; search right subtree
55
+ return self._find_interval(node.right, point, length)
56
+
57
+ def _insert(self, node: Optional[EarliestIntervalNode],
58
+ new_node: EarliestIntervalNode) -> EarliestIntervalNode:
59
+ node = super()._insert(node, new_node)
60
+ node.update_stats() # Update the earliest-specific stats
61
+ return node
62
+
63
+
64
+ # Example usage:
65
+ if __name__ == "__main__":
66
+ tree = EarliestIntervalTree()
67
+ # Initially, the whole interval [0, 100) is available
68
+ tree.release_interval(0, 100)
69
+ print("Initial tree:")
70
+ tree.print_tree()
71
+ print(f"Total available length: {tree.get_total_available_length()}")
72
+
73
+ # Schedule interval [0, 1
74
+ tree.reserve_interval(0, 1)
75
+ print("\nAfter scheduling [0, 1]:")
76
+ tree.print_tree()
77
+ print(f"Total available length: {tree.get_total_available_length()}")
78
+
79
+ # Unschedule interval [0, 1]
80
+ tree.release_interval(0, 1)
81
+ print("\nAfter unscheduling [0, 1]:")
82
+ tree.print_tree()
83
+ print(f"Total available length: {tree.get_total_available_length()}")
84
+
85
+ # Schedule interval [1, 2]
86
+ tree.reserve_interval(1, 3)
87
+ print("\nAfter scheduling [1, 3]:")
88
+ tree.print_tree()
89
+ print(f"Total available length: {tree.get_total_available_length()}")
90
+
91
+ # Schedule interval [2, 3]
92
+ tree.reserve_interval(2, 5)
93
+ print("\nAfter scheduling [2, 5]:")
94
+ tree.print_tree()
95
+ print(f"Total available length: {tree.get_total_available_length()}")
96
+
97
+
98
+ # Schedule interval [10, 20)
99
+ tree.reserve_interval(10, 20)
100
+ print("\nAfter scheduling [10, 20):")
101
+ tree.print_tree()
102
+ print(f"Total available length: {tree.get_total_available_length()}")
103
+
104
+ # Schedule interval [15, 25)
105
+ tree.reserve_interval(15, 25)
106
+ print("\nAfter scheduling [15, 25):")
107
+ tree.print_tree()
108
+ print(f"Total available length: {tree.get_total_available_length()}")
109
+
110
+ # Find interval starting at or after 18 with length at least 5
111
+ result = tree.find_interval(18, 5)
112
+ if result:
113
+ print(f"\nFound interval: [{result.start}, {result.end})")
114
+ else:
115
+ print("\nNo suitable interval found.")
116
+
117
+ # Unschedule interval [10, 20)
118
+ tree.release_interval(10, 20)
119
+ print("\nAfter unscheduling [10, 20):")
120
+ tree.print_tree()
121
+ print(f"Total available length: {tree.get_total_available_length()}")
122
+
123
+ # Delete interval overlapping multiple intervals
124
+ tree.reserve_interval(5, 15)
125
+ print("\nAfter deleting interval [5, 15):")
126
+ tree.print_tree()
127
+ print(f"Total available length: {tree.get_total_available_length()}")
@@ -0,0 +1,76 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Generic, List, Optional, Tuple, TypeVar, Protocol
3
+
4
+ class IntervalNodeProtocol(Protocol):
5
+ start: int
6
+ end: int
7
+ length: int
8
+ height: int
9
+ total_length: int
10
+ left: Optional['IntervalNodeProtocol']
11
+ right: Optional['IntervalNodeProtocol']
12
+
13
+ def update_stats(self) -> None: ...
14
+ def update_length(self) -> None: ...
15
+
16
+ T = TypeVar('T', bound=IntervalNodeProtocol)
17
+
18
+ class IntervalNodeBase(Generic[T]):
19
+ def __init__(self, start: int, end: int) -> None:
20
+ self.start: int = start
21
+ self.end: int = end
22
+ self.length: int = end - start
23
+ self._height: int = 1
24
+ self._total_length: int = self.length
25
+
26
+ self.left: Optional[T] = None
27
+ self.right: Optional[T] = None
28
+
29
+ @property
30
+ @abstractmethod
31
+ def height(self) -> int:
32
+ return self._height
33
+
34
+ @height.setter
35
+ def height(self, value: int) -> None:
36
+ self._height = value
37
+
38
+ @property
39
+ @abstractmethod
40
+ def total_length(self) -> int:
41
+ return self._total_length
42
+
43
+ @total_length.setter
44
+ def total_length(self, value: int) -> None:
45
+ self._total_length = value
46
+
47
+ def update_length(self) -> None:
48
+ self.length = self.end - self.start
49
+
50
+
51
+ class IntervalTreeBase(Generic[T], ABC):
52
+ def __init__(self, root: Optional[T] = None) -> None:
53
+ self.root: Optional[T] = root
54
+
55
+ def print_tree(self) -> None:
56
+ self._print_tree(self.root)
57
+
58
+ def _print_tree(self, node: Optional[T], indent: str = "", prefix: str = "") -> None:
59
+ if node is None:
60
+ return
61
+
62
+ self._print_tree(node.right, indent + " ", "┌── ") # type: ignore
63
+ self._print_node(node, indent, prefix)
64
+ self._print_tree(node.left, indent + " ", "└── ") # type: ignore
65
+
66
+ def get_total_available_length(self) -> int:
67
+ if not self.root:
68
+ return 0
69
+ return self.root.total_length
70
+
71
+ @abstractmethod
72
+ def _print_node(self, node: T, indent: str, prefix: str) -> None: ...
73
+
74
+ @abstractmethod
75
+ def get_intervals(self) -> List[Tuple[int, int]]: ...
76
+
@@ -0,0 +1,171 @@
1
+ from sortedcontainers import SortedDict
2
+ from typing import List, Optional, Tuple
3
+
4
+ class IntervalManager:
5
+ def __init__(self) -> None:
6
+ # Intervals are stored as {start: end}
7
+ self.intervals: SortedDict[int, int] = SortedDict()
8
+ self.total_available_length: int = 0
9
+
10
+ def release_interval(self, start: int, end: int) -> None:
11
+ if start >= end:
12
+ return
13
+
14
+ # Find position to insert or merge
15
+ idx = self.intervals.bisect_left(start)
16
+
17
+ # Check and merge with previous interval if overlapping or adjacent
18
+ if idx > 0:
19
+ prev_start = self.intervals.keys()[idx - 1]
20
+ prev_end = self.intervals[prev_start]
21
+ if prev_end >= start:
22
+ start = prev_start
23
+ end = max(end, prev_end)
24
+ idx -= 1
25
+ del self.intervals[prev_start]
26
+ self.total_available_length -= prev_end - prev_start
27
+
28
+ # Merge with next intervals if overlapping
29
+ while idx < len(self.intervals):
30
+ curr_start = self.intervals.keys()[idx]
31
+ curr_end = self.intervals[curr_start]
32
+ if curr_start > end:
33
+ break
34
+ end = max(end, curr_end)
35
+ del self.intervals[curr_start]
36
+ self.total_available_length -= curr_end - curr_start
37
+
38
+ # Insert the new merged interval
39
+ self.intervals[start] = end
40
+ self.total_available_length += end - start
41
+
42
+ def reserve_interval(self, start: int, end: int) -> None:
43
+ if start >= end:
44
+ return
45
+
46
+ idx = self.intervals.bisect_left(start)
47
+
48
+ if idx > 0:
49
+ prev_start = self.intervals.keys()[idx - 1]
50
+ prev_end = self.intervals[prev_start]
51
+ if prev_end > start:
52
+ idx -= 1
53
+
54
+ intervals_to_add: List[Tuple[int, int]] = []
55
+ keys_to_delete: List[int] = []
56
+
57
+ while idx < len(self.intervals):
58
+ curr_start = self.intervals.keys()[idx]
59
+ curr_end = self.intervals[curr_start]
60
+
61
+ if curr_start >= end:
62
+ break
63
+
64
+ overlap_start = max(start, curr_start)
65
+ overlap_end = min(end, curr_end)
66
+
67
+ if overlap_start < overlap_end:
68
+ # Mark current interval for removal
69
+ keys_to_delete.append(curr_start)
70
+ self.total_available_length -= curr_end - curr_start
71
+
72
+ # Add non-overlapping intervals
73
+ if curr_start < start:
74
+ intervals_to_add.append((curr_start, start))
75
+ if curr_end > end:
76
+ intervals_to_add.append((end, curr_end))
77
+
78
+ idx += 1
79
+
80
+ # Remove intervals after iteration
81
+ for key in keys_to_delete:
82
+ del self.intervals[key]
83
+
84
+ # Add new intervals
85
+ for s, e in intervals_to_add:
86
+ self.intervals[s] = e
87
+ self.total_available_length += e - s
88
+
89
+ def find_interval(self, point: int, length: int) -> Optional[Tuple[int, int]]:
90
+ idx = self.intervals.bisect_left(point)
91
+ intervals_keys = self.intervals.keys()
92
+
93
+ # Check the interval at idx
94
+ if idx < len(intervals_keys):
95
+ s = intervals_keys[idx]
96
+ e = self.intervals[s]
97
+ if s <= point < e and e - point >= length:
98
+ return point, point + length
99
+ elif s > point and e - s >= length:
100
+ return s, s + length
101
+
102
+ # Check the previous interval
103
+ if idx > 0:
104
+ idx -= 1
105
+ s = intervals_keys[idx]
106
+ e = self.intervals[s]
107
+ if s <= point < e and e - point >= length:
108
+ return point, point + length
109
+ elif point < s and e - s >= length:
110
+ return s, s + length
111
+
112
+ return None
113
+
114
+ def get_total_available_length(self) -> int:
115
+ return self.total_available_length
116
+
117
+ def print_intervals(self) -> None:
118
+ print("Available intervals:")
119
+ for s, e in self.intervals.items():
120
+ print(f"[{s}, {e})")
121
+ print(f"Total available length: {self.total_available_length}")
122
+
123
+ def get_intervals(self) -> List[Tuple[int, int]]:
124
+ return list(self.intervals.items())
125
+
126
+ # Example usage:
127
+ if __name__ == "__main__":
128
+ manager = IntervalManager()
129
+ # Initially, the whole interval [0, 100) is available
130
+ manager.release_interval(0, 100)
131
+ print("Initial intervals:")
132
+ manager.print_intervals()
133
+
134
+ # Schedule interval [10, 20)
135
+ manager.reserve_interval(10, 20)
136
+ print("\nAfter scheduling [10, 20):")
137
+ manager.print_intervals()
138
+
139
+ # Schedule interval [15, 25)
140
+ manager.reserve_interval(15, 25)
141
+ print("\nAfter scheduling [15, 25):")
142
+ manager.print_intervals()
143
+
144
+ # Unschedule interval [10, 20)
145
+ manager.release_interval(10, 20)
146
+ print("\nAfter unscheduling [10, 20):")
147
+ manager.print_intervals()
148
+
149
+ # Schedule two adjacent intervals [30, 40) and [40, 50)
150
+ manager.reserve_interval(30, 40)
151
+ manager.reserve_interval(40, 50)
152
+ print("\nAfter scheduling two adjacent intervals [30, 40) and [40, 50):")
153
+ manager.print_intervals()
154
+
155
+ # Release overlapping interval [35, 45)
156
+ manager.release_interval(35, 45)
157
+ print("\nAfter releasing overlapping interval [35, 45):")
158
+ manager.print_intervals()
159
+
160
+ # Test finding intervals
161
+ result = manager.find_interval(0, 15)
162
+ print(f"\nFinding interval of length 15 starting from 0: {result}")
163
+
164
+ # Test complete overlap
165
+ manager.reserve_interval(60, 80)
166
+ print("\nAfter reserving [60, 80):")
167
+ manager.print_intervals()
168
+
169
+ manager.release_interval(65, 75)
170
+ print("\nAfter releasing internal interval [65, 75):")
171
+ manager.print_intervals()
@@ -0,0 +1,100 @@
1
+ from typing import Optional
2
+ from base import IntervalNodeBase, IntervalTreeBase
3
+
4
+
5
+ class SegmentTreeNode(IntervalNodeBase['SegmentTreeNode']):
6
+ def __init__(self, start: int, end: int) -> None:
7
+ super().__init__(start, end)
8
+ self.total_length: int = self.length
9
+ self.is_full: bool = True
10
+
11
+ def update_node(self) -> None:
12
+ if self.left is not None and self.right is not None:
13
+ self.total_length = self.left.total_length + self.right.total_length
14
+ self.is_full = self.left.is_full and self.right.is_full
15
+ else:
16
+ self.total_length = self.length if self.is_full else 0
17
+
18
+ class SegmentTree(IntervalTreeBase[SegmentTreeNode]):
19
+ def __init__(self, start: int, end: int) -> None:
20
+ super().__init__(SegmentTreeNode(start, end))
21
+
22
+ def _print_node(self, node: SegmentTreeNode, indent: str, prefix: str) -> None:
23
+ print(f"{indent}{prefix}{node.start}-{node.end} (len={node.length}, total_len={node.total_length}, is_full={node.is_full})")
24
+
25
+ def _build(self, node: SegmentTreeNode | None) -> None:
26
+ if node is None:
27
+ return
28
+ if node.end - node.start <= 1:
29
+ return
30
+ mid: int = (node.start + node.end) // 2
31
+ node.left = SegmentTreeNode(node.start, mid)
32
+ node.right = SegmentTreeNode(mid, node.end)
33
+ self._build(node.left)
34
+ self._build(node.right)
35
+
36
+ def build(self) -> None:
37
+ self._build(self.root)
38
+
39
+ def _update(self, node: Optional[SegmentTreeNode], start: int, end: int, is_full: bool) -> None:
40
+ if node is None:
41
+ return
42
+ if node.end <= start or node.start >= end:
43
+ return
44
+ if node.start >= start and node.end <= end:
45
+ node.is_full = is_full
46
+ node.total_length = node.length if is_full else 0
47
+ node.left = None
48
+ node.right = None
49
+ else:
50
+ if node.left is None or node.right is None:
51
+ mid: int = (node.start + node.end) // 2
52
+ node.left = SegmentTreeNode(node.start, mid)
53
+ node.right = SegmentTreeNode(mid, node.end)
54
+ node.left.is_full = node.right.is_full = node.is_full
55
+ node.left.total_length = (node.left.end - node.left.start) if node.left.is_full else 0
56
+ node.right.total_length = (node.right.end - node.right.start) if node.right.is_full else 0
57
+ self._update(node.left, start, end, is_full)
58
+ self._update(node.right, start, end, is_full)
59
+ node.update_node()
60
+
61
+ def schedule_interval(self, start: int, end: int) -> None:
62
+ self._update(self.root, start, end, False)
63
+
64
+ def unschedule_interval(self, start: int, end: int) -> None:
65
+ self._update(self.root, start, end, True)
66
+
67
+
68
+
69
+ # Example usage:
70
+ if __name__ == "__main__":
71
+ # Initialize segment tree with interval [0, 100)
72
+ tree = SegmentTree(0, 100)
73
+ tree.build()
74
+ print("Initial tree:")
75
+ tree.print_tree()
76
+ print(f"Total available length: {tree.get_total_available_length()}")
77
+
78
+ # Schedule interval [10, 20)
79
+ tree.schedule_interval(10, 20)
80
+ print("\nAfter scheduling [10, 20):")
81
+ tree.print_tree()
82
+ print(f"Total available length: {tree.get_total_available_length()}")
83
+
84
+ # Schedule interval [30, 40)
85
+ tree.schedule_interval(30, 40)
86
+ print("\nAfter scheduling [30, 40):")
87
+ tree.print_tree()
88
+ print(f"Total available length: {tree.get_total_available_length()}")
89
+
90
+ # Unschedule interval [10, 20)
91
+ tree.unschedule_interval(10, 20)
92
+ print("\nAfter unscheduling [10, 20):")
93
+ tree.print_tree()
94
+ print(f"Total available length: {tree.get_total_available_length()}")
95
+
96
+ # Split at pivot 50 (schedule [50, 50))
97
+ tree.schedule_interval(50, 50)
98
+ print("\nAfter splitting at pivot 50:")
99
+ tree.print_tree()
100
+ print(f"Total available length: {tree.get_total_available_length()}")
File without changes
@@ -0,0 +1,129 @@
1
+ // IntervalManager class implementation
2
+ #include <map>
3
+ #include <vector>
4
+ #include <optional>
5
+ #include <iostream>
6
+
7
+ class IntervalManager {
8
+ public:
9
+ IntervalManager() : total_available_length(0) {}
10
+
11
+ void release_interval(int start, int end) {
12
+ if (start >= end) return;
13
+
14
+ auto it = intervals.lower_bound(start);
15
+
16
+ // Merge with previous interval if overlapping or adjacent
17
+ if (it != intervals.begin()) {
18
+ auto prev_it = std::prev(it);
19
+ if (prev_it->second >= start) {
20
+ start = prev_it->first;
21
+ end = std::max(end, prev_it->second);
22
+ total_available_length -= prev_it->second - prev_it->first;
23
+ intervals.erase(prev_it);
24
+ }
25
+ }
26
+
27
+ // Merge with overlapping intervals
28
+ while (it != intervals.end() && it->first <= end) {
29
+ end = std::max(end, it->second);
30
+ total_available_length -= it->second - it->first;
31
+ it = intervals.erase(it);
32
+ }
33
+
34
+ intervals[start] = end;
35
+ total_available_length += end - start;
36
+ }
37
+
38
+ void reserve_interval(int start, int end) {
39
+ if (start >= end) return;
40
+
41
+ auto it = intervals.lower_bound(start);
42
+
43
+ if (it != intervals.begin()) {
44
+ auto prev_it = std::prev(it);
45
+ if (prev_it->second > start) {
46
+ it = prev_it;
47
+ }
48
+ }
49
+
50
+ std::vector<std::map<int, int>::iterator> to_erase;
51
+ std::vector<std::pair<int, int>> to_add;
52
+
53
+ while (it != intervals.end() && it->first < end) {
54
+ int curr_start = it->first;
55
+ int curr_end = it->second;
56
+
57
+ int overlap_start = std::max(start, curr_start);
58
+ int overlap_end = std::min(end, curr_end);
59
+
60
+ if (overlap_start < overlap_end) {
61
+ to_erase.push_back(it);
62
+ total_available_length -= curr_end - curr_start;
63
+
64
+ if (curr_start < start) {
65
+ to_add.emplace_back(curr_start, start);
66
+ }
67
+ if (curr_end > end) {
68
+ to_add.emplace_back(end, curr_end);
69
+ }
70
+ }
71
+ ++it;
72
+ }
73
+
74
+ for (auto& eit : to_erase) {
75
+ intervals.erase(eit);
76
+ }
77
+ for (const auto& interval : to_add) {
78
+ intervals[interval.first] = interval.second;
79
+ total_available_length += interval.second - interval.first;
80
+ }
81
+ }
82
+
83
+ std::optional<std::pair<int, int>> find_interval(int point, int length) {
84
+ auto it = intervals.lower_bound(point);
85
+
86
+ if (it != intervals.end()) {
87
+ int s = it->first;
88
+ int e = it->second;
89
+ if (s <= point && e - point >= length) {
90
+ return std::make_pair(point, point + length);
91
+ } else if (s > point && e - s >= length) {
92
+ return std::make_pair(s, s + length);
93
+ }
94
+ }
95
+
96
+ if (it != intervals.begin()) {
97
+ --it;
98
+ int s = it->first;
99
+ int e = it->second;
100
+ if (s <= point && e - point >= length) {
101
+ return std::make_pair(point, point + length);
102
+ } else if (point < s && e - s >= length) {
103
+ return std::make_pair(s, s + length);
104
+ }
105
+ }
106
+
107
+ return std::nullopt;
108
+ }
109
+
110
+ int get_total_available_length() const {
111
+ return total_available_length;
112
+ }
113
+ void print_intervals() const {
114
+ std::ostream& out = std::cout;
115
+ out << "Available intervals:\n";
116
+ for (const auto& [s, e] : intervals) {
117
+ out << "[" << s << ", " << e << ")\n";
118
+ }
119
+ out << "Total available length: " << total_available_length << "\n";
120
+ }
121
+
122
+ std::vector<std::pair<int, int>> get_intervals() const {
123
+ return std::vector<std::pair<int, int>>(intervals.begin(), intervals.end());
124
+ }
125
+
126
+ private:
127
+ std::map<int, int> intervals;
128
+ int total_available_length;
129
+ };
@@ -0,0 +1,31 @@
1
+ // Pybind11 bindings for IntervalManager
2
+ #include <pybind11/pybind11.h>
3
+ #include <pybind11/stl.h>
4
+ #ifdef WITH_IC_MANAGER
5
+ #include "boundary_ic.cpp" // Include the IntervalManager implementation
6
+ #endif
7
+ #include "boundary.cpp" // Include the IntervalManager implementation
8
+
9
+ namespace py = pybind11;
10
+
11
+ PYBIND11_MODULE(boundary, m) {
12
+ py::class_<IntervalManager>(m, "IntervalManager")
13
+ .def(py::init<>())
14
+ .def("release_interval", &IntervalManager::release_interval)
15
+ .def("reserve_interval", &IntervalManager::reserve_interval)
16
+ .def("find_interval", &IntervalManager::find_interval)
17
+ .def("get_total_available_length", &IntervalManager::get_total_available_length)
18
+ .def("print_intervals", &IntervalManager::print_intervals)
19
+ .def("get_intervals", &IntervalManager::get_intervals);
20
+
21
+ #ifdef WITH_IC_MANAGER
22
+ py::class_<ICIntervalManager>(m, "ICIntervalManager")
23
+ .def(py::init<>())
24
+ .def("release_interval", &ICIntervalManager::release_interval)
25
+ .def("reserve_interval", &ICIntervalManager::reserve_interval)
26
+ .def("find_interval", &ICIntervalManager::find_interval)
27
+ .def("get_total_available_length", &ICIntervalManager::get_total_available_length)
28
+ .def("print_intervals", &ICIntervalManager::print_intervals)
29
+ .def("get_intervals", &ICIntervalManager::get_intervals);
30
+ #endif
31
+ }
@@ -0,0 +1,65 @@
1
+ // IntervalManager class implementation using Boost Interval Containers
2
+ #include <boost/icl/interval_set.hpp>
3
+ #include <boost/icl/interval.hpp>
4
+ #include <iostream>
5
+ #include <vector>
6
+ #include <optional>
7
+
8
+ class ICIntervalManager {
9
+ public:
10
+ ICIntervalManager() : total_available_length(0) {}
11
+
12
+ void release_interval(int start, int end) {
13
+ if (start >= end) return;
14
+ auto interval = boost::icl::interval<int>::right_open(start, end);
15
+
16
+ intervals.add(interval);
17
+
18
+ total_available_length = intervals.size();
19
+ }
20
+
21
+ void reserve_interval(int start, int end) {
22
+ if (start >= end) return;
23
+ auto interval = boost::icl::interval<int>::right_open(start, end);
24
+
25
+ intervals.subtract(interval);
26
+
27
+ total_available_length = intervals.size();
28
+ }
29
+
30
+ std::optional<std::pair<int, int>> find_interval(int point, int length) {
31
+ auto it = intervals.find(point);
32
+ if (it != intervals.end()) {
33
+ int s = it->lower();
34
+ int e = it->upper();
35
+ if (e - point >= length) {
36
+ return std::make_pair(point, point + length);
37
+ }
38
+ }
39
+ return std::nullopt;
40
+ }
41
+
42
+ int get_total_available_length() const {
43
+ return total_available_length;
44
+ }
45
+
46
+ void print_intervals() const {
47
+ std::cout << "Available intervals:\n";
48
+ for (const auto& interval : intervals) {
49
+ std::cout << "[" << interval.lower() << ", " << interval.upper() << ")\n";
50
+ }
51
+ std::cout << "Total available length: " << total_available_length << "\n";
52
+ }
53
+
54
+ std::vector<std::pair<int, int>> get_intervals() const {
55
+ std::vector<std::pair<int, int>> result;
56
+ for (const auto& interval : intervals) {
57
+ result.emplace_back(interval.lower(), interval.upper());
58
+ }
59
+ return result;
60
+ }
61
+
62
+ private:
63
+ boost::icl::interval_set<int> intervals;
64
+ int total_available_length;
65
+ };