Skip to content

Commit 3abc026

Browse files
authored
Added ArraySegmentTree and RangeQueryDynamic (#483)
1 parent 8c91a6a commit 3abc026

File tree

7 files changed

+485
-5
lines changed

7 files changed

+485
-5
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Prashant Rawat <prashant.rawat216@gmail.com>
99
Harsheet <harsheetkakar@gmail.com>
1010
Pratik Goyal <pratikgoyal2712@gmail.com>
1111
Jay Thorat <j.thorat10@gmail.com>
12+
Rajveer Singh Bharadwaj <rsb3256@gmail.com>

pydatastructs/miscellaneous_data_structures/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@
3434
)
3535
__all__.extend(sparse_table.__all__)
3636

37+
from .segment_tree import (
38+
ArraySegmentTree,
39+
)
40+
__all__.extend(segment_tree.__all__)
41+
3742
from .algorithms import (
38-
RangeQueryStatic
43+
RangeQueryStatic,
44+
RangeQueryDynamic
3945
)
4046
__all__.extend(algorithms.__all__)

pydatastructs/miscellaneous_data_structures/algorithms.py

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from pydatastructs.miscellaneous_data_structures.sparse_table import SparseTable
2+
from pydatastructs.miscellaneous_data_structures.segment_tree import ArraySegmentTree
23
from pydatastructs.utils.misc_util import (
34
_check_range_query_inputs, Backend,
45
raise_if_backend_is_not_python)
56

6-
__all__ = ['RangeQueryStatic']
7+
__all__ = [
8+
'RangeQueryStatic',
9+
'RangeQueryDynamic'
10+
]
711

812

913
class RangeQueryStatic:
@@ -159,3 +163,173 @@ def query(self, start, end):
159163
for i in range(start + 2, end + 1):
160164
query_ans = self.func((query_ans, self.array[i]))
161165
return query_ans
166+
167+
class RangeQueryDynamic:
168+
"""
169+
Produces results for range queries of different kinds
170+
while allowing point updates by using specified
171+
data structure.
172+
173+
Parameters
174+
==========
175+
176+
array: OneDimensionalArray
177+
The array for which we need to answer queries.
178+
All the elements should be of type `int`.
179+
func: callable
180+
The function to be used for generating results
181+
of a query. It should accept only one tuple as an
182+
argument. The size of the tuple will be either 1 or 2
183+
and any one of the elements can be `None`. You can treat
184+
`None` in whatever way you want according to the query
185+
you are performing. For example, in case of range minimum
186+
queries, `None` can be treated as infinity. We provide
187+
the following which can be used as an argument value for this
188+
parameter,
189+
190+
`minimum` - For range minimum queries.
191+
192+
`greatest_common_divisor` - For queries finding greatest
193+
common divisor of a range.
194+
195+
`summation` - For range sum queries.
196+
data_structure: str
197+
The data structure to be used for performing
198+
range queries.
199+
Currently the following data structures are supported,
200+
201+
'array' -> Array data structure.
202+
Each query takes O(end - start) time asymptotically.
203+
Each point update takes O(1) time asymptotically.
204+
205+
'segment_tree' -> Segment tree data structure.
206+
Each query takes O(log(end - start)) time
207+
asymptotically.
208+
Each point update takes O(log(len(array))) time
209+
asymptotically.
210+
211+
By default, 'segment_tree'.
212+
backend: pydatastructs.Backend
213+
The backend to be used.
214+
Optional, by default, the best available
215+
backend is used.
216+
217+
Examples
218+
========
219+
220+
>>> from pydatastructs import OneDimensionalArray, RangeQueryDynamic
221+
>>> from pydatastructs import minimum
222+
>>> arr = OneDimensionalArray(int, [4, 6, 1, 5, 7, 3])
223+
>>> RMQ = RangeQueryDynamic(arr, minimum)
224+
>>> RMQ.query(3, 4)
225+
5
226+
>>> RMQ.query(0, 4)
227+
1
228+
>>> RMQ.query(0, 2)
229+
1
230+
>>> RMQ.update(2, 0)
231+
>>> RMQ.query(0, 2)
232+
0
233+
234+
Note
235+
====
236+
237+
The array once passed as an input should be modified
238+
only with `RangeQueryDynamic.update` method.
239+
"""
240+
241+
def __new__(cls, array, func, data_structure='segment_tree', **kwargs):
242+
raise_if_backend_is_not_python(
243+
cls, kwargs.get('backend', Backend.PYTHON))
244+
245+
if len(array) == 0:
246+
raise ValueError("Input %s array is empty."%(array))
247+
248+
if data_structure == 'array':
249+
return RangeQueryDynamicArray(array, func, **kwargs)
250+
elif data_structure == 'segment_tree':
251+
return RangeQueryDynamicSegmentTree(array, func, **kwargs)
252+
else:
253+
raise NotImplementedError(
254+
"Currently %s data structure for range "
255+
"query with point updates isn't implemented yet."
256+
% (data_structure))
257+
258+
@classmethod
259+
def methods(cls):
260+
return ['query', 'update']
261+
262+
def query(start, end):
263+
"""
264+
Method to perform a query in [start, end) range.
265+
266+
Parameters
267+
==========
268+
269+
start: int
270+
The starting index of the range.
271+
end: int
272+
The ending index of the range.
273+
"""
274+
raise NotImplementedError(
275+
"This is an abstract method.")
276+
277+
def update(self, index, value):
278+
"""
279+
Method to update index with a new value.
280+
281+
Parameters
282+
==========
283+
284+
index: int
285+
The index to be update.
286+
value: int
287+
The new value.
288+
"""
289+
raise NotImplementedError(
290+
"This is an abstract method.")
291+
292+
class RangeQueryDynamicArray(RangeQueryDynamic):
293+
294+
__slots__ = ["range_query_static"]
295+
296+
def __new__(cls, array, func, **kwargs):
297+
raise_if_backend_is_not_python(
298+
cls, kwargs.get('backend', Backend.PYTHON))
299+
obj = object.__new__(cls)
300+
obj.range_query_static = RangeQueryStaticArray(array, func)
301+
return obj
302+
303+
@classmethod
304+
def methods(cls):
305+
return ['query', 'update']
306+
307+
def query(self, start, end):
308+
return self.range_query_static.query(start, end)
309+
310+
def update(self, index, value):
311+
self.range_query_static.array[index] = value
312+
313+
class RangeQueryDynamicSegmentTree(RangeQueryDynamic):
314+
315+
__slots__ = ["segment_tree", "bounds"]
316+
317+
def __new__(cls, array, func, **kwargs):
318+
raise_if_backend_is_not_python(
319+
cls, kwargs.pop('backend', Backend.PYTHON))
320+
obj = object.__new__(cls)
321+
obj.segment_tree = ArraySegmentTree(array, func, dimensions=1)
322+
obj.segment_tree.build()
323+
obj.bounds = (0, len(array))
324+
return obj
325+
326+
@classmethod
327+
def methods(cls):
328+
return ['query', 'update']
329+
330+
def query(self, start, end):
331+
_check_range_query_inputs((start, end + 1), self.bounds)
332+
return self.segment_tree.query(start, end)
333+
334+
def update(self, index, value):
335+
self.segment_tree.update(index, value)

0 commit comments

Comments
 (0)