我想在std::set
容器中存储一堆范围项。
通过重载std::set
的比较,以便使用set::find
方法检查一个数据集,此数据结构应可以快速确定该集合当前所保存的某个范围内是否包含特定的输入范围。集合中的项目包含输入范围参数。
它还应该支持代表单个点的范围项目(起始范围==终止范围)。
这是我的实现:
#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)中)
但是,对于更宽的范围,我目前的运算符<
能够找到contained
术语的equal
关系set
(这意味着对于范围a和b a<b && a<b
。
是否有必要更改此运算符以使其正常工作?
答案 0 :(得分:8)
听起来像使用Boost Interval Container Library的完美搭配。简而言之,您可以
#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
,并且无需进一步调整即可使用。
答案 1 :(得分:7)
您的operator <
定义了部分顺序:
(30,45) < (40, 50) == false
并同时(40, 50) < (30, 45) == false
,因此就std::set
和std::map
而言,它们是相等的。这就是为什么您得到这些结果的原因。
有一篇关于偏序的论文:https://en.wikipedia.org/wiki/Partially_ordered_set
您可能想使用std::unordered_map
或以某种方式定义范围的总订单。
我建议operator <
比较范围边界的算术平均值,即
(a,b)<(c,d)当且仅当(a + b)/ 2 <(c + d)/ 2为总阶数。请注意,您可能希望将float用于算术平均值。
为了进行测试,我建议使用以下代码草案(我从头开始编写但未对其进行测试)。 -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;
}
答案 2 :(得分:4)
如果您希望基于C ++的排序使用任何容器或算法,则排序关系必须为Strict Weak Ordering Relation。可以在Wikipedia上找到该定义,简而言之,必须遵守以下规则:
您的比较运算符失败,因此不适合。通常,获得良好的比较运算符的快速方法是执行元组的操作:
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)时间内找到最接近的密钥,并从此快速确定包含状态。
对于不相交的间隔,我担心事情会变得更加棘手,您将需要开始研究专门的数据结构(例如,间隔树)。