对范围项使用 std::set 容器

Using std::set container for range items

本文关键字:set 容器 std 范围      更新时间:2023-10-16

我想在容器std::set存储一堆范围项目。

此数据结构应通过重载std::set的比较来快速确定特定输入范围是否由集合当前持有的范围之一包含,以便使用set::find方法检查集合中的项目之一是否包含输入范围参数。

它还应支持表示单个点 (start_range == end_range) 的范围项。

这是我的实现:

#include <iostream>
#include <map>
#include <set>
using std::set;
using std::map;
class range : public std::pair<int,int>
{
public:
range(int lower, int upper)
{
if (upper < lower)
{
first = upper;
second = lower;
}
else
{
first = lower;
second = upper;
}
}
range(int val)
{
first = second = val;
}
bool operator<(range const & b) const
{
if (second < b.first)
{
return true;
}
return false;
}
};

以下是我测试数据结构的方法:

int main(int argc, const char * argv[])
{
std::map<int, std::set<range>> n;
n[1].insert(range(-50,-40));
n[1].insert(range(40,50));
n[2].insert(range(-30,-20));
n[2].insert(range(20,30));
n[3].insert(range(-20,-10));
n[3].insert(range(10,20));
range v[] = {range(-50,-41), range(30,45), range(-45,-45), range(25,25)};
int j[] = {1,2,3};
for (int l : j)
{
for (range i : v)
{
if (n[l].find(i) != n[l].end())
{
std::cout << l << "," << i.first << ","  << i.second << " : " 
<< n[l].find(range(i))->first  << " "
<< n[l].find(range(i))->second << std::endl;
}
}
}
}

这是我得到的结果:

1,-50,-41 : -50 -40 --> good 
1,30,45 : 40 50     --> bad
1,-45,-45 : -50 -40 --> good
2,30,45 : 20 30     --> bad
2,25,25 : 20 30     --> good

如您所见,我的代码确实很好地支持单点范围(-45 包含在范围 (-50,-40) 中,25 包含在范围 (20,30)中)

但是,对于更广泛的范围,我当前的运算符<能够找到equalset术语的contained关系(这意味着对于范围 a 和 ba<b && a<b.

有没有办法更改此运算符以使其工作?

听起来像是使用提升间隔容器库的完美匹配。简而言之,您可以

#include <boost/icl/interval_set.hpp>
// Helper function template to reduce explicit typing:
template <class T>
auto closed(T&& lower, T&& upper)
{
return boost::icl::discrete_interval<T>::closed(std::forward<T>(lower),
std::forward<T>(upper));
}
boost::icl::interval_set<int> ranges;
ranges.insert(closed(1, 2));
ranges.insert(closed(42, 50));
std::cout << contains(ranges, closed(43, 46)) << "n"; // true
std::cout << contains(ranges, closed(42, 54)) << "n"; // false

这应该很容易插入您的std::map,并且无需进一步调整即可使用。

您的operator <定义了部分顺序:(30,45) < (40, 50) == false,同时(40, 50) < (30, 45) == falsestd::setstd::map它们是平等的。这就是你得到这些结果的原因。

有一篇关于偏序的论文:https://en.wikipedia.org/wiki/Partially_ordered_set

您可能希望使用std::unordered_map或以某种方式定义范围的总顺序。

我建议operator <比较范围边界的算术平均值,即 (a, b) <(c, d) 当且仅当 (a+b)/2 <(c+d)/2 表示总订单。请注意,您可能希望将浮点数用于算术平均值。

为了进行测试,我建议使用以下代码草稿(我在这里从头开始编写,没有对其进行测试)。 -1 表示没有包含this的范围

int range::firstContainsMe(const std::vector<range> rangesVec)
{
for (size_t i = 0; i < rangesVec; i++) {
if (lower >= rangesVec[i].lower && upper <= rangesVec[i].upper) {
return i;
}
}
return -1;
}

您的比较运算符不合适。

如果您希望在C++中使用任何基于排序的容器或算法,则排序关系必须是严格弱排序关系。该定义可以在维基百科上找到,简而言之,必须遵守以下规则:

  • 不可反身性:对于 S 中的所有 x,并不是 x
  • 不对称性:对于 S 中的所有 x,y 如果 x <y,则>
  • 传递性:对于 S 中的所有 x、y、z,如果 x<z,则><z。>
  • 不可比性的传递性:对于 S 中的所有 x、y、z,如果 x 与 y 不可比较(x 既不

比较运算符失败,因此不合适。通常,获得良好比较运算符的快速方法是执行元组的作用:

bool operator<(range const & b) const
{
return std::tie(first, second) < std::tie(b.first, b.second);
}

你想要一张地图,而不是一套。

为了解决你的问题,你想要一张地图,而不是一套。

对于不相交区间,从下界到上限的映射就足够了:

std::map<int, int> intervals;

.lower_bound.upper_bound操作允许在O(log N)时间内找到最近的密钥,并从那里快速断言包含。

对于非不相交区间,我担心事情会变得更加棘手,你会想要开始研究专门的数据结构(例如区间树)。