Увеличить поиск multi_index_container для записей, которые попадают в интервалы, определенные двумя полями
Рассмотрим следующую таблицу:
id F1 F2
0 0 10
1 5 20
2 20 30
3 8 13
4 13 17
5 50 65
6 15 26
7 8 15
Поиск записей, которые имеют x, где F1 <= x && x <= F2. Например, поиск записей с x = 10 даст записи с идентификаторами 0,1,3,7.
Как вы реализуете это в C++, используя boost multi_index_container, чтобы избежать линейного поиска?
2 ответа
Вам понадобится много данных, чтобы сделать это стоящим, но я думаю, это то, что вы просите:
#include <utility>
#include <vector>
#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <boost/multi_index/member.hpp>
struct record
{
int id;
int f1, f2;
};
struct record_fs_extractor
{
using result_type = std::pair<int, int>;
constexpr result_type operator ()(record const& r) const noexcept
{
return {r.f1, r.f2};
}
};
namespace bmi = boost::multi_index;
using records_t = bmi::multi_index_container<
record,
bmi::indexed_by<
bmi::ordered_unique<bmi::member<record, int, &record::id>>,
bmi::ordered_non_unique<bmi::tag<struct record_fs_tag>, record_fs_extractor>
>
>;
std::vector<int> find_ids(records_t const& records, int const x)
{
// second index's interface is like std::map<std::pair<int, int>, record>,
// except that duplicate keys are allowed f1--^ ^--f2
auto const& f_idx = records.get<record_fs_tag>();
auto it = f_idx.lower_bound(std::make_pair(f_idx.cbegin()->f1, x));
auto const it_end = f_idx.cend();
std::vector<int> ret;
while (it != it_end && it->f1 <= x)
{
if (x <= it->f2)
ret.push_back(it++->id);
else
it = f_idx.lower_bound(std::make_pair(it->f1, x));
}
return ret;
}
int main()
{
records_t const records
{
{ 0, 0, 10 },
{ 1, 5, 20 },
{ 2, 20, 30 },
{ 3, 8, 13 },
{ 4, 13, 17 },
{ 5, 50, 65 },
{ 6, 15, 26 },
{ 7, 8, 15 }
};
// default index's interface is like std::map<int, record>
std::cout << "all, ordered by id:\n"; // id--^
for (auto const& r : records)
std::cout << '\t' << r.id << ": " << r.f1 << ", " << r.f2 << '\n';
std::cout << "\nreturned by find_ids(10):";
for (auto const& id : find_ids(records, 10))
std::cout << ' ' << id;
std::cout << '\n';
}
Выход:
all, ordered by id:
0: 0, 10
1: 5, 20
2: 20, 30
3: 8, 13
4: 13, 17
5: 50, 65
6: 15, 26
7: 8, 15
returned by find_ids(10): 0 1 3 7
IFF, обнаруживший первую запись, оказывается настолько узким местом, что стоит затрат на память третьего индекса, поэтому здесь есть альтернативная реализация для решения этой проблемы.
Вы пытаетесь пересечь интервалы. Почему вы не используете представление, которое соответствует домену? См., Например, http://www.boost.org/doc/libs/1_60_0/libs/icl/doc/html/index.html
Другой способ сделать это - использовать геометрический запрос. Вы можете представить интервалы в виде отрезков (или блоков для большего количества измерений) и запросить их. См., Например, http://www.boost.org/doc/libs/1_60_0/libs/geometry/doc/html/geometry/reference/spatial_indexes.html
@cv_and_he: Да, именно это я имел в виду с помощью подхода rtree:
#include <iostream>
#include <initializer_list>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptor/map.hpp>
struct record {
int id;
int f1, f2;
};
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<int, 2, bg::cs::cartesian> point;
typedef bg::model::segment<point> segment;
struct interval_finder
{
typedef std::pair<segment, int> value;
bgi::rtree<value, bgi::quadratic<16> > rt;
interval_finder(std::initializer_list<record> l) {
for(const record& rec : l)
rt.insert(value { {point(rec.f1), point(rec.f2)}, rec.id});
}
auto find(int val) const {
return boost::copy_range<std::vector<int> >(rt
| bgi::adaptors::queried(bgi::intersects(point(val)))
| boost::adaptors::map_values
);
}
};
int main() {
interval_finder finder{
{ 0, 0, 10 },
{ 1, 5, 20 },
{ 2, 20, 30 },
{ 3, 8, 13 },
{ 4, 13, 17 },
{ 5, 50, 65 },
{ 6, 15, 26 },
{ 7, 8, 15 }
};
for (auto& p : finder.rt)
std::cout << p.second << "\t" << bg::wkt(p.first) << "\n";
for(auto range : finder.find(10))
std::cout << range << " ";
}
В демонстрационных целях я печатаю элементы индекса, чтобы вы могли понять, как он представляет интервалы в виде сегментов:
0 LINESTRING(0 0,10 0)
1 LINESTRING(5 0,20 0)
2 LINESTRING(20 0,30 0)
3 LINESTRING(8 0,13 0)
4 LINESTRING(13 0,17 0)
5 LINESTRING(50 0,65 0)
6 LINESTRING(15 0,26 0)
7 LINESTRING(8 0,15 0)
0 1 3 7