Skip to content
Open

RMQ #12

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
6 changes: 6 additions & 0 deletions pysdsl/__init__.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "types/intvector.hpp"
#include "types/suffixarray.hpp"
#include "types/wavelet.hpp"
#include "types/rmq.hpp"

namespace py = pybind11;

Expand Down Expand Up @@ -43,6 +44,11 @@ PYBIND11_MODULE(pysdsl, m)

auto csa_classes = add_csa(m);

auto rmq_classes_tuple = add_rmq_classes(m, iv_classes_as_params);
auto& rmq_sparse_tables = std::get<0>(rmq_classes_tuple);
auto& rmq_sada = std::get<1>(rmq_classes_tuple);
auto& rmq_sct = std::get<2>(rmq_classes_tuple);

for_each_in_tuple(iv_classes, make_inits_many_functor(iv_classes));
for_each_in_tuple(iv_classes, make_inits_many_functor(enc_classes));
for_each_in_tuple(iv_classes,
Expand Down
15 changes: 15 additions & 0 deletions pysdsl/docstrings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,18 @@ const char* doc_csa_wt(
"A class for the Compressed Suffix Array (CSA) based on a Wavelet Tree "
"(WT) of the Burrow Wheeler Transform of the original text."
);

const char* doc_rmq_sada(
"A class to support range minimum or range maximum queries on a random access container "
"in constant time and 4n+o(n) bits space."
);

const char* doc_rmq_sparse_table(
"A class to support range minimum or range maximum queries on a random access container "
"in constant time and O( n log^2(n) ) bits space."
);

const char* doc_rmq_sct(
"A class to support range minimum or range maximum queries on a random access container "
"in constant time and 2n+o(n) bits space."
);
236 changes: 236 additions & 0 deletions pysdsl/types/rmq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#pragma once

#include <string>
#include <tuple>
#include <utility>

#include <pybind11/pybind11.h>

#include <sdsl/rmq_support_sparse_table.hpp>
#include <sdsl/rmq_succinct_sada.hpp>

#include "operations/sizes.hpp"
#include "operations/iteration.hpp"
#include "util/tupletricks.hpp"
#include "docstrings.hpp"
#include "io.hpp"
#include "calc.hpp"



namespace detail {
// adds constructors of t_rac... containers
template <typename PybindClass, typename... t_rac>
typename std::enable_if<sizeof...(t_rac) == 0>::type add_rac_constructor(const PybindClass&) {}

template <typename PybindClass, typename t_rac_head, typename... t_rac_tail>
void add_rac_constructor(PybindClass& cls)
{
cls.def(py::init([](const t_rac_head* rac) {
return typename PybindClass::type(rac);
}));
add_rac_constructor<PybindClass, t_rac_tail...>(cls);
}

} // namespace details


// containers names for sparse
namespace RAC_names {
constexpr char INT_VECTOR_NAME[] = "IntVector",
INT16_VECTOR_NAME[] = "Int16Vector";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we inherit them from int_vectors tuple?

}


struct add_rmq_sparse_table_functor {
py::module& m;
const char* doc;

constexpr add_rmq_sparse_table_functor(py::module& m, const char* doc = nullptr)
: m(m), doc(doc) {}

template <typename t_rac, const char* rac_name, bool t_min>
decltype(auto) operator()(std::tuple<t_rac,
std::integral_constant<const char*, rac_name>,
std::integral_constant<bool, t_min>>)
{
using Table = sdsl::rmq_support_sparse_table<t_rac, t_min>;
using size_type = typename Table::size_type;

std::string name =
std::string("Range") + (t_min ? "Min" : "Max") + "QuerySparseTable_for_" + rac_name;

auto cls = py::class_<Table>(m, name.c_str())
.def(py::init([](const t_rac* rac) {return Table(rac);}))
.def("set_vector", &Table::set_vector,
"Sets a vector rmq is processed on.")
.def("__call__",
(size_type (Table::*)(size_type, size_type) const)& Table::operator(),
(std::string("Returns an index of the ") + (t_min ? "minimal" : "maximal") +
" value on the segment [l,r].").c_str());

add_sizes(cls);
add_description(cls);

// load takes two params
// add_serialization(cls);

cls.doc() = doc;

std::string key = std::string(t_min ? "Min" : "Max") + "_in_" + rac_name;

m.attr("rmq_sparse_tables").attr("__setitem__")(key, cls);
m.attr((std::string("all_range_") + (t_min ? "min" : "max") + "_classes").c_str())
.attr("append")(cls);

return cls;
}
};


struct add_rmq_sada_functor {
py::module& m;
const char* doc;

constexpr add_rmq_sada_functor(py::module& m, const char* doc = nullptr)
: m(m), doc(doc) {}


template <typename... t_rac, bool t_min>
decltype(auto) operator()(std::tuple<std::tuple<t_rac...>,
std::integral_constant<bool, t_min>>)
{
using RMQClass = typename std::conditional<t_min, sdsl::rmq_succinct_sada<>,
typename sdsl::range_maximum_support_sada<>::type>::type;
using size_type = typename RMQClass::size_type;

std::string name =
std::string("Range") + (t_min ? "Min" : "Max") + "QuerySuccintSada";

auto cls = py::class_<RMQClass>(m, name.c_str())
.def(py::init())
.def("__call__",
(size_type (RMQClass::*)(size_type, size_type) const)& RMQClass::operator(),
(std::string("Returns an index of the ") + (t_min ? "minimal" : "maximal") +
" value on the segment [l,r].").c_str());;

detail::add_rac_constructor<decltype(cls), t_rac...>(cls);

add_sizes(cls);
add_description(cls);
add_serialization(cls);

cls.doc() = doc;

m.attr("rmq_sada").attr("__setitem__")(t_min ? "Min" : "Max", cls);
m.attr((std::string("all_range_") + (t_min ? "min" : "max") + "_classes").c_str())
.attr("append")(cls);

return cls;
}
};


struct add_rmq_sct_functor {
py::module& m;
const char* doc;

constexpr add_rmq_sct_functor(py::module& m, const char* doc = nullptr)
: m(m), doc(doc) {}


template <typename... t_rac, bool t_min>
decltype(auto) operator()(std::tuple<std::tuple<t_rac...>,
std::integral_constant<bool, t_min>>)
{
using RMQClass = typename std::conditional<t_min, sdsl::rmq_succinct_sct<>,
typename sdsl::range_maximum_sct<>::type>::type;
using size_type = typename RMQClass::size_type;

std::string name =
std::string("Range") + (t_min ? "Min" : "Max") + "QuerySuccintSct";

auto cls = py::class_<RMQClass>(m, name.c_str())
.def(py::init())
.def("__call__",
(size_type (RMQClass::*)(size_type, size_type) const)& RMQClass::operator(),
(std::string("Returns an index of the ") + (t_min ? "minimal" : "maximal") +
" value on the segment [l,r].").c_str());


detail::add_rac_constructor<decltype(cls), t_rac...>(cls);

add_sizes(cls);
add_description(cls);
add_serialization(cls);

cls.doc() = doc;

m.attr("rmq_sct").attr("__setitem__")(t_min ? "Min" : "Max", cls);
m.attr((std::string("all_range_") + (t_min ? "min" : "max") + "_classes").c_str())
.attr("append")(cls);

return cls;
}
};


// generalized (constants -> typenames) template for usage with GeneralSubsetFunctor
template <typename t_rac, typename t_min_integral_constant>
using general_rmq_sparse_table =
py::class_<sdsl::rmq_support_sparse_table<t_rac, t_min_integral_constant::value>>;

template <typename t_min_integral_constant>
using general_rmq_sada = py::class_<
typename std::conditional<t_min_integral_constant::value,
sdsl::rmq_succinct_sada<>,
typename sdsl::range_maximum_support_sada<>::type>::type>;

template <typename t_min_integral_constant>
using general_rmq_sct = py::class_<
typename std::conditional<t_min_integral_constant::value,
sdsl::rmq_succinct_sct<>,
typename sdsl::range_maximum_sct<>::type>::type>;


template <typename... Ts>
static
auto make_rmq_params(const std::tuple<py::class_<Ts>&...>& params) {
return std::tuple<std::tuple<std::tuple<Ts...>, std::integral_constant<bool, true>>,
std::tuple<std::tuple<Ts...>, std::integral_constant<bool, false>>>();
}


template <typename... Ts>
inline auto add_rmq_classes(py::module& m, const std::tuple<py::class_<Ts>&...>& params)
{
m.attr("rmq_sparse_tables") = py::dict();
m.attr("rmq_sada") = py::dict();
m.attr("rmq_sct") = py::dict();
m.attr("all_range_min_classes") = py::list();
m.attr("all_range_max_classes") = py::list();

using rmq_support_sparse_table_params = std::tuple<
std::tuple<sdsl::int_vector<>,
std::integral_constant<const char*, RAC_names::INT_VECTOR_NAME>,
std::integral_constant<bool, true>>,
std::tuple<sdsl::int_vector<>,
std::integral_constant<const char*, RAC_names::INT_VECTOR_NAME>,
std::integral_constant<bool, false>>,
std::tuple<sdsl::int_vector<16>,
std::integral_constant<const char*, RAC_names::INT16_VECTOR_NAME>,
std::integral_constant<bool, true>>,
std::tuple<sdsl::int_vector<16>,
std::integral_constant<const char*, RAC_names::INT16_VECTOR_NAME>,
std::integral_constant<bool, false>>
>;

auto rmq_sparse_tables = for_each_in_tuple(rmq_support_sparse_table_params(),
add_rmq_sparse_table_functor(m, doc_rmq_sparse_table));
auto rmq_sada_classes = for_each_in_tuple(make_rmq_params(params),
add_rmq_sada_functor(m, doc_rmq_sada));
auto rmq_sct_classes = for_each_in_tuple(make_rmq_params(params),
add_rmq_sct_functor(m, doc_rmq_sct));

return std::make_tuple(rmq_sparse_tables, rmq_sada_classes, rmq_sct_classes);
}
1 change: 0 additions & 1 deletion pysdsl/util/tupletricks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,3 @@ template <template <typename...> typename general_template, typename... Ts>
auto make_general_subset_functor(std::tuple<Ts...>& tpl) {
return GeneralSubsetFunctor<general_template, Ts...>(tpl);
}

65 changes: 65 additions & 0 deletions tests/test_rmq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import random
import pytest

from pysdsl import Int8Vector, Int64Vector
from pysdsl import rmq_sada, rmq_sct




def _test_rmq(rmq_class, container_class, target):
cont = container_class(50)
a = []

for i in range(50):
a.append(random.randint(0, 50))
cont[i] = a[i]

rmq = rmq_class(cont)

for _ in range(50):
l = random.randint(0, 49)
r = random.randint(l, 49)

i = rmq(l, r)

assert cont[i] == target(a[l:r+1])



@pytest.mark.parametrize('container_class', (Int8Vector, Int64Vector))
def test_rmq_sada(container_class):
_test_rmq(rmq_sada['Min'], container_class, min)
_test_rmq(rmq_sada['Max'], container_class, max)


@pytest.mark.parametrize('container_class', (Int8Vector, Int64Vector))
def test_rmq_sct(container_class):
_test_rmq(rmq_sct['Min'], container_class, min)
_test_rmq(rmq_sct['Max'], container_class, max)



@pytest.mark.parametrize("rmq_class", list(rmq_sada.values()) + list(rmq_sct.values()))
def test_rmq_on_container_remove(rmq_class):
cont = Int64Vector(3)

cont[0] = 1
cont[1] = 2
cont[2] = 3

rmq = rmq_class(cont)

ans1 = rmq(0, 2)

cont[0] = 3
cont[1] = 2
cont[2] = 1

ans2 = rmq(0, 2)
assert ans1 == ans2

cont = None

ans2 = rmq(0, 2)
assert ans1 == ans2