|
1 | 1 | from pydatastructs.miscellaneous_data_structures.sparse_table import SparseTable
|
| 2 | +from pydatastructs.miscellaneous_data_structures.segment_tree import ArraySegmentTree |
2 | 3 | from pydatastructs.utils.misc_util import (
|
3 | 4 | _check_range_query_inputs, Backend,
|
4 | 5 | raise_if_backend_is_not_python)
|
5 | 6 |
|
6 |
| -__all__ = ['RangeQueryStatic'] |
| 7 | +__all__ = [ |
| 8 | + 'RangeQueryStatic', |
| 9 | + 'RangeQueryDynamic' |
| 10 | +] |
7 | 11 |
|
8 | 12 |
|
9 | 13 | class RangeQueryStatic:
|
@@ -159,3 +163,173 @@ def query(self, start, end):
|
159 | 163 | for i in range(start + 2, end + 1):
|
160 | 164 | query_ans = self.func((query_ans, self.array[i]))
|
161 | 165 | 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