Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You can install the library by running the following command,
python -m pip install .
```

For development purposes, you can use the option `develop` as shown below,
For development purposes, you can use the option `e` as shown below,

```python
python -m pip install -e .
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Algorithms
==========

.. autoclass:: pydatastructs.RangeQueryStatic
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ Miscellaneous Data Structures
stack.rst
queue.rst
binomial_trees.rst
disjoint_set.rst
disjoint_set.rst
sparse_table.rst
algorithms.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SparseTable
===========

.. autoclass:: pydatastructs.SparseTable
13 changes: 12 additions & 1 deletion pydatastructs/miscellaneous_data_structures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
stack,
binomial_trees,
queue,
disjoint_set
disjoint_set,
sparse_table
)

from .binomial_trees import (
Expand All @@ -27,3 +28,13 @@
DisjointSetForest,
)
__all__.extend(disjoint_set.__all__)

from .sparse_table import (
SparseTable,
)
__all__.extend(sparse_table.__all__)

from .algorithms import (
RangeQueryStatic
)
__all__.extend(algorithms.__all__)
153 changes: 153 additions & 0 deletions pydatastructs/miscellaneous_data_structures/algorithms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from pydatastructs.miscellaneous_data_structures.sparse_table import SparseTable
from pydatastructs.utils.misc_util import _check_range_query_inputs

__all__ = ['RangeQueryStatic']


class RangeQueryStatic:
"""
Produces results for range queries of different kinds
by using specified data structure.

Parameters
==========

array: OneDimensionalArray
The array for which we need to answer queries.
All the elements should be of type `int`.
func: callable
The function to be used for generating results
of a query. It should accept only one tuple as an
argument. The size of the tuple will be either 1 or 2
and any one of the elements can be `None`. You can treat
`None` in whatever way you want according to the query
you are performing. For example, in case of range minimum
queries, `None` can be treated as infinity. We provide
the following which can be used as an argument value for this
parameter,

`minimum` - For range minimum queries.

`greatest_common_divisor` - For queries finding greatest
common divisor of a range.

`summation` - For range sum queries.
data_structure: str
The data structure to be used for performing
range queries.
Currently the following data structures are supported,

'array' -> Array data structure.
Each query takes O(end - start) time asymptotically.

'sparse_table' -> Sparse table data structure.
Each query takes O(log(end - start)) time
asymptotically.

By default, 'sparse_table'.

Examples
========

>>> from pydatastructs import OneDimensionalArray, RangeQueryStatic
>>> from pydatastructs import minimum
>>> arr = OneDimensionalArray(int, [4, 6, 1, 5, 7, 3])
>>> RMQ = RangeQueryStatic(arr, minimum)
>>> RMQ.query(3, 5)
5
>>> RMQ.query(0, 5)
1
>>> RMQ.query(0, 3)
1

Note
====

The array once passed as an input should not be modified
once the `RangeQueryStatic` constructor is called. If you
have updated the array, then you need to create a new
`RangeQueryStatic` object with this updated array.
"""

def __new__(cls, array, func, data_structure='sparse_table'):
if len(array) == 0:
raise ValueError("Input %s array is empty."%(array))

if data_structure == 'array':
return RangeQueryStaticArray(array, func)
elif data_structure == 'sparse_table':
return RangeQueryStaticSparseTable(array, func)
else:
raise NotImplementedError(
"Currently %s data structure for range "
"query without updates isn't implemented yet."
% (data_structure))

@classmethod
def methods(cls):
return ['query']

def query(start, end):
"""
Method to perform a query in [start, end) range.

Parameters
==========

start: int
The starting index of the range.
end: int
The index just before which the range ends.
This means that this index will be excluded
from the range for generating results.
"""
raise NotImplementedError(
"This is an abstract method.")


class RangeQueryStaticSparseTable(RangeQueryStatic):

__slots__ = ["sparse_table", "bounds"]

def __new__(cls, array, func):
obj = object.__new__(cls)
sparse_table = SparseTable(array, func)
obj.bounds = (0, len(array))
obj.sparse_table = sparse_table
return obj

@classmethod
def methods(cls):
return ['query']

def query(self, start, end):
_check_range_query_inputs((start, end), self.bounds)
return self.sparse_table.query(start, end)


class RangeQueryStaticArray(RangeQueryStatic):

__slots__ = ["array", "func"]

def __new__(cls, array, func):
obj = object.__new__(cls)
obj.array = array
obj.func = func
return obj

@classmethod
def methods(cls):
return ['query']

def query(self, start, end):
_check_range_query_inputs((start, end), (0, len(self.array)))

rsize = end - start

if rsize == 1:
return self.func((self.array[start],))

query_ans = self.func((self.array[start], self.array[start + 1]))
for i in range(start + 2, end):
query_ans = self.func((query_ans, self.array[i]))
return query_ans
104 changes: 104 additions & 0 deletions pydatastructs/miscellaneous_data_structures/sparse_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from pydatastructs.linear_data_structures.arrays import (
MultiDimensionalArray, OneDimensionalArray)
from pydatastructs.utils.misc_util import NoneType
import math

__all__ = ['SparseTable']


class SparseTable(object):
"""
Represents the sparse table data structure.

Parameters
==========

array: OneDimensionalArray
The array to be used for filling the sparse table.
func: callable
The function to be used for filling the sparse table.
It should accept only one tuple as an argument. The
size of the tuple will be either 1 or 2 and any one
of the elements can be `None`. You can treat `None` in
whatever way you want. For example, in case of minimum
values, `None` can be treated as infinity. We provide
the following which can be used as an argument value for this
parameter,

`minimum` - For range minimum queries.

`greatest_common_divisor` - For queries finding greatest
common divisor of a range.

`summation` - For range sum queries.

Examples
========

>>> from pydatastructs import SparseTable, minimum
>>> from pydatastructs import OneDimensionalArray
>>> arr = OneDimensionalArray(int, [1, 2, 3, 4, 5])
>>> s_t = SparseTable(arr, minimum)
>>> str(s_t)
"['[1, 1, 1]', '[2, 2, 2]', '[3, 3, None]', '[4, 4, None]', '[5, None, None]']"

References
==========

.. [1] https://cp-algorithms.com/data_structures/sparse-table.html
"""

__slots__ = ['_table', 'func']

def __new__(cls, array, func):

if len(array) == 0:
raise ValueError("Input %s array is empty."%(array))

obj = object.__new__(cls)
size = len(array)
log_size = int(math.log2(size)) + 1
obj._table = [OneDimensionalArray(int, log_size) for _ in range(size)]
obj.func = func

for i in range(size):
obj._table[i][0] = func((array[i],))

for j in range(1, log_size + 1):
for i in range(size - (1 << j) + 1):
obj._table[i][j] = func((obj._table[i][j - 1],
obj._table[i + (1 << (j - 1))][j - 1]))

return obj

@classmethod
def methods(cls):
return ['query', '__str__']

def query(self, start, end):
"""
Method to perform a query on sparse table in [start, end)
range.

Parameters
==========

start: int
The starting index of the range.
end: int
The index just before which the range ends.
This means that this index will be excluded
from the range for generating results.
"""
end -= 1
j = int(math.log2(end - start + 1)) + 1
answer = None
while j >= 0:
if start + (1 << j) - 1 <= end:
answer = self.func((answer, self._table[start][j]))
start += 1 << j
j -= 1
return answer

def __str__(self):
return str([str(array) for array in self._table])
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from pydatastructs import (
RangeQueryStatic, minimum,
greatest_common_divisor, summation,
OneDimensionalArray)
from pydatastructs.utils.raises_util import raises
import random, math

def _test_RangeQueryStatic_common(func, gen_expected):

array = OneDimensionalArray(int, [])
raises(ValueError, lambda: RangeQueryStatic(array, func))

array = OneDimensionalArray(int, [1])
rq = RangeQueryStatic(array, func)
assert rq.query(0, 1) == 1
raises(ValueError, lambda: rq.query(0, 0))
raises(IndexError, lambda: rq.query(0, 2))

array_sizes = [3, 6, 12, 24, 48, 96]
random.seed(0)
for array_size in array_sizes:
data = random.sample(range(-2*array_size, 2*array_size), array_size)
array = OneDimensionalArray(int, data)

expected = []
inputs = []
for i in range(array_size):
for j in range(i + 1, array_size):
inputs.append((i, j))
expected.append(gen_expected(data, i, j))

data_structures = ["array", "sparse_table"]
for ds in data_structures:
rmq = RangeQueryStatic(array, func, data_structure=ds)
for input, correct in zip(inputs, expected):
assert rmq.query(input[0], input[1]) == correct

def test_RangeQueryStatic_minimum():

def _gen_minimum_expected(data, i, j):
return min(data[i:j])

_test_RangeQueryStatic_common(minimum, _gen_minimum_expected)

def test_RangeQueryStatic_greatest_common_divisor():

def _gen_gcd_expected(data, i, j):
if j - i == 1:
return data[i]
else:
expected_gcd = math.gcd(data[i], data[i + 1])
for idx in range(i + 2, j):
expected_gcd = math.gcd(expected_gcd, data[idx])
return expected_gcd

_test_RangeQueryStatic_common(greatest_common_divisor, _gen_gcd_expected)

def test_RangeQueryStatic_summation():

def _gen_summation_expected(data, i, j):
return sum(data[i:j])

return _test_RangeQueryStatic_common(summation, _gen_summation_expected)
5 changes: 4 additions & 1 deletion pydatastructs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
CartesianTreeNode,
RedBlackTreeNode,
TrieNode,
SkipNode
SkipNode,
summation,
greatest_common_divisor,
minimum
)
__all__.extend(misc_util.__all__)
Loading