Why is this std::map key not getting found? - c++

I asked a similar question once before and now I have a related problem. Below, the output is "not found" and the number of elements printed is 2.
The line positions.emplace(r,q); clearly inserts the element and the map size is correct, so why is r not found? p is found (not logged in this example).
#include <map>
#include <iostream>
struct screenPoint {
float x = 0, y = 0;
screenPoint(float x_, float y_): x{x_}, y{y_}{}
};
bool operator<(const screenPoint& left, const screenPoint& right){
return left.x<right.x||left.y<right.y;
}
std::map<screenPoint, screenPoint> positions;
int main(int argc, const char * argv[]) {
auto p = screenPoint(593,271.5);
auto q = screenPoint(595.5,269.5);
auto r = screenPoint(599,267);
positions.emplace(p,q);
positions.emplace(r,q);
auto f = positions.find(r);
if (f == positions.end()){
std::cout << "not found";
} else {
std::cout << "found";
}
std::cout << std::endl;
std::cout << "number elements: " << positions.size() << "\n";
return 0;
}

Your operator< is not a Strict Weak Ordering. You have for example both p<q and q<p. This means Undefined Behavior for any map operations.
One way to provide a valid operator<, ignoring NaNs, would be:
bool operator<(const screenPoint& left, const screenPoint& right) {
return left.x < right.x ||
( left.x == right.x && left.y < right.y );
}

Your comparison operator
bool operator<(const screenPoint& left, const screenPoint& right){
return left.x<right.x||left.y<right.y;
}
Is incorrect. You need to use an if statement and if the x's are equal return if left.y is less than right.y otherwise return left.x < right.x. or use std::tie like
bool operator<(const screenPoint& left, const screenPoint& right){
return std::tie(left.x, left.y) < std::tie(right.x, right.y);
}

Related

Iterating over std::map with custom less operator implementation gives less elements that it contains

Assuming I have the following simple program (http://cpp.sh/5sygh):
#include <map>
#include <iostream>
using Key = std::pair<unsigned long, unsigned long long>;
struct KeyLess {
bool operator()(const Key& lhs, const Key& rhs) {
if (lhs.first < rhs.first) {
return true;
}
if (lhs.second < rhs.second) {
return true;
}
return false;
}
};
int main() {
std::map< Key , int, KeyLess> m;
m[Key{2, 169}] = 1;
m[Key{1, 255}] = 2;
m[Key{1, 391}] = 3;
m[Key{1, 475}] = 4;
std::cout << "Elements in map: " << m.size() << std::endl;
for(const auto &x: m) {
std::cout <<"Value: "<< x.second << std::endl;
}
}
The output contains only 2 items instead of 4 in the map:
Elements in map: 4
Value: 2
Value: 1
What do I miss here?
Your less operator should be:
struct KeyLess {
bool operator()(const Key& lhs, const Key& rhs) {
if (lhs.first < rhs.first) {
return true;
}
if (lhs.first == rhs.first && lhs.second < rhs.second) {
return true;
}
return false;
}
};
When you compare structures with multiple elements it might help to think of structures as words and elements as characters.
With this modification, the less operator works lexicographically, the way you compare two words of the same length when you sort them: you continue the comparison on the next position while the words have the same character at the current position and decide when the characters at the current position differ. If you reach the end of both words, the words are equal.
Your compare function does not meet the requirements of strict weak ordering.
In SWO, if A < B, and B < C, then A must be less than C. Key equality is also checked by seeing if two values are not less than each other. If (!(a<b) && !(b<a)) then a == b. Two keys should not both be less than each other.
For your keys and using your compare function
Key{2, 169} < Key{1, 255} // this is true because 169 < 255
Key{1, 255} < Key{2, 169} // this is also true because 1 < 2
Obviously this is a problem, since both of these keys compare less than each other using your comparator.
My suggested solution: since your keys are std::pairs, you shouldn't need to define a new comparator. std::pair already uses lexicographical compare by default.
You could hide the intricacies of the comparator and solve the bug (already explained by #MarkoMahnič) by making use of std::tie.
bool operator()(const Key& lhs, const Key& rhs)
{
return std::tie(lhs.first, lhs.second) < std::tie(rhs.first, rhs.second);
}
Your comparator doesn't meet the requirements of std::map, it needs to provide a strict weak ordering. Fortunately std::tuple implements this for you if you need to compare multiple values:
struct KeyLess {
bool operator()(const Key& lhs, const Key& rhs) {
return std::tie(lhs.first, lhs.second) < std::tie(rhs.first, rhs.second);
}
};
In your case you don't actually need a custom comparator at all as std::pair's < operator already has the same behaviour.
Your KeyLess code result in incorrect comparison:
KeyLess cmp;
std::cout << cmp(Key{2, 169},Key{1, 391})<< std::endl; // yields true
std::cout << cmp(Key{1, 391},Key{2, 169})<< std::endl; // yields true
When both comparisons yields false, it means that keys are equal, when they yield true, behavior of map iterator is not defined. It's related to the fact that map sorts its elements.
Note that operator()required to be const or program might not compile with standard C++17 and later. As a possible variant:
#include <map>
#include <iostream>
using Key = std::pair<unsigned long, unsigned long long>;
struct KeyLess {
bool operator()(const Key& lhs, const Key& rhs) const {
if (lhs.first < rhs.first) {
return true;
}
else if (lhs.first > rhs.first)
return false;
if (lhs.second < rhs.second) {
return true;
}
return false;
}
};
int main()
{
std::map< Key , int, KeyLess > m;
m[Key{2, 169}] = 1;
m[Key{1, 255}] = 2;
m[Key{1, 391}] = 3;
m[Key{1, 475}] = 4;
std::cout << "Elements in map: " << m.size() << std::endl;
for(const auto &[key, value]: m) {
std::cout << "Key: " << key.first << ", " << key.second << " Value: "<< value << std::endl;
}
}

How to use a custom class as key with std::map if there is no logical way to have a comparison operator defined?

I'm trying to use std::map with a custom class and in the course of the process the program has to call std::map::find in order to resolve a key to a pair. The custom class doesn't seem to fit well in terms of comparisons.
This is probably better explained in code; I have a class that I want to use as a key:
class index_t
{
int vertex_index;
int normal_index;
int texture_index;
}
std::map<index_t, int> reindexer;
I would like to use
reindexer.find(index_to_find);
In order to find a key with exactly same parameters (exactly same vertex/normal/texture indices) exists in the map already.
So technically I want the std::map::find function to behave like this:
bool find(key_to_find) //this is what I'm expecting from a find function of std::map
{
if(existing_key.vertex == key_to_find.vertex && existing_key.texture == key_to_find.texture && existing_key.normal == key_to_find.normal)
return true;
else return false;
}
However, I'm not sure how to overload the comparison operator appropriately in this situation for it to behave like that (since I can think of no logical less than operator that would suit this class). This is the current operator I'm using:
bool operator<(const index_t& rhv)
{
if(vertex_index < rhv && normal_index < rhv && texture_index < rhv)
return true;
else return false;
}
It doesn't work, since the find relies on the function returning "false" reflexively when comparison orders reversed.
How can I get around this?
This is some more specific, compilable code that reproduces the problem:
class index_t
{
public:
int vertex;
int normal;
int texture;
bool operator< (const index_t& rhv) const
{
if (vertex < rhv.vertex && normal < rhv.normal && texture < rhv.texture)
return true;
else return false;
}
};
map<index_t, int> indexMap;
int main()
{
index_t i;
i.vertex = 0;
i.normal = 0;
i.texture = 0;
index_t i2;
i2.vertex = 1;
i2.normal = 0;
i2.texture = 3;
index_t i4;
i4.vertex = 1;
i4.normal = 0;
i4.texture = 3;
index_t i5;
i5.vertex = 6;
i5.normal = 0;
i5.texture = 3;
index_t i8;
i8.vertex = 7;
i8.normal = 5;
i8.texture = 4;
indexMap.insert(pair<index_t, int>(i, 0));
indexMap.insert(pair<index_t, int > (i2, 1));
if (indexMap.find(i5) != indexMap.end())
cout << "found" << endl;
else
cout << "not found" << endl;
system("pause");
return 0;
}
This results in "found" even though i5 is not a part of the map
I also tried this:
class index_t
{
public:
int vertex;
int normal;
int texture;
};
class index_comparator
{
public:
bool operator()(const index_t& lhv, const index_t& rhv) const
{
if (lhv.vertex == rhv.vertex && lhv.normal == rhv.normal && lhv.texture == rhv.texture)
return true;
else return false;
}
};
map<index_t, int, index_comparator> indexMap;
int main()
{
index_t i;
i.vertex = 0;
i.normal = 0;
i.texture = 0;
index_t i2;
i2.vertex = 1;
i2.normal = 0;
i2.texture = 3;
index_t i4;
i4.vertex = 1;
i4.normal = 0;
i4.texture = 3;
index_t i5;
i5.vertex = 6;
i5.normal = 0;
i5.texture = 3;
index_t i8;
i8.vertex = 7;
i8.normal = 5;
i8.texture = 4;
indexMap.insert(pair<index_t, int>(i, 0));
indexMap.insert(pair<index_t, int > (i2, 1));
if (indexMap.find(i5) != indexMap.end())
cout << "found" << endl;
else
cout << "not found" << endl;
system("pause");
return 0;
}
This also results in "found"
The expected results are that when I call std::map::find on a custom class it compares it other keys in the map and only returns true if an exactly same class (containing the same parameters) exists. Otherwise it should return false.
You have to define a strict order to use class index_t as key in a std::map.
It doesn't need to make sense to you – it just has to provide a unique result of less-than for any pairs of index_t instances (and to grant a < b && b < c => a < c).
The (in question) exposed attempt doesn't seem to fulfil this but the following example would:
bool operator<(const index_t &index1, const index_t &index2)
{
if (index1.vertex != index2.vertex) return index1.vertex < index2.vertex;
if (index1.normal != index2.normal) return index1.normal < index2.normal;
return index1.texture < index2.texture;
}
The simplest way to implement the operator is with tuples, it does all the hard work for you:
bool operator<(const index_t& rhv)
{
return std::tie(vertex_index, normal_index, texture_index) < std::tie(rhv.vertex_index, rhv.normal_index, rhv.texture_index);
}
This is equivalent to the required logic:
bool operator<(const index_t& rhv)
{
if (vertex_index != rhv.vertex_index)
{
return vertex_index < rhv.vertex_index;
}
if (normal_index!= rhv.normal_index)
{
return normal_index< rhv.normal_index;
}
return texture_index< rhv.texture_index;
}
In c++20 this gets even easier with the spaceship operator which does everything for you:
auto operator<=>(const index_t&) const = default;
Your ordering doesn't fulfill the requirements, it has to be what is called a "strict weak ordering relation". It's easiest to not implement that yourself, but instead use existing functionality. Examle:
#include <tuple>
bool operator()(const index_t& lhv, const index_t& rhv) const
{
return std::tie(lhv.vertex, lhv.normal, lhv.texture) <
std::tie(rhv.vertex, rhv.normal, rhv.texture);
}
Your comparison function doesn't have to be logical, it just has to impose a strict weak ordering. Here's a version that works.
bool operator<(const index_t& rhv) const
{
if (vertex < rhv.vertex)
return true;
if (vertex > rhv.vertex)
return false;
if (normal < rhv.normal)
return true;
if (normal > rhv.normal)
return false;
if (texture < rhv.texture)
return true;
if (texture > rhv.texture)
return false;
return false;
}
Since this is not a reasonable operator< for your class it would be better to rename it, to avoid confusion.
struct IndexLT
{
bool operator()(const index_t& lhs, const index_t& rhs)
{
// logic as before
}
};
Then use this newly declared functor like this
std::map<index_t, whatever, IndexLT> my_map;
Yet another alternative would be to use a std::unordered_map since ordering doesn't seem to be significant.

C++ sort pair with respect to two criteria

I have the following vector with pair values:
first 3 second 2
first 1 second 2
first 1 second 1
first 2 second 2
I would like to sort my vector such that the result would be
==========================
first 1 second 2
first 1 second 1
first 2 second 2
first 3 second 2
That means:
sort with respect to the first element.
in case of equality sort with respect to the second element
My code looks like:
#include <utility> // std::pair
#include <iostream> // std::cout
#include <vector>
typedef std::pair<double, double> my_pair;
struct sort_pred
{
bool operator () (const my_pair& left, const my_pair& right)
{
return (left.first < right.first) && (left.second > right.second);
}
};
int main () {
std::vector<my_pair> data;
data.push_back(my_pair(3,2) );
data.push_back(my_pair(1,2) );
data.push_back(my_pair(1,1) );
data.push_back(my_pair(2,2) );
for(auto a: data)
std::cout << "first "<< a.first << " second " << a.second << std::endl;
std::cout << "==========================\n";
std::sort(data.begin(), data.end(), sort_pred());
for(auto a: data)
std::cout << "first "<< a.first << " second " << a.second << std::endl;
return 0;
}
The condition in the sort_pred expressed what I would like to do, but is not correct. I get wrong values.
Any idea how this can be easily solved?
Your comparator isn't quite right, since you want it to return true if either the first check succeeds OR the first are equal and the second check succeeds. You only want to check the seconds if the firsts are equal. So something like this:
struct sort_pred
{
bool operator()(const my_pair& left, const my_pair& right) const
{
if (left.first != right.first) {
return left.first < right.first;
}
return left.second > right.second;
}
};
This can be simplified using the fact that tuples are lexicographically comparable:
struct sort_pred
{
bool operator()(const my_pair& left, const my_pair& right) const
{
return std::tie(left.first, right.second) <
std::tie(right.first, left.second);
}
};
This is possible with a bit of tweaking to your predicate:
struct sort_pred
{
bool operator () (const my_pair& lhs, const my_pair& rhs)
{
return lhs.first<rhs.first ||
(!(rhs.first<lhs.first) && lhs.second>rhs.second);
}
};
Live demo
Your use of && doesn't really make sense. It goes on to evaluate the second element if left.first > right.first, but really you want that to only happen if left.first == right.first.
This should work:
bool operator () (const my_pair& left, const my_pair& right)
{
if (left.first != right.first)
return left.first < right.first;
else
return left.second > right.second;
}
You have to distinguish more cases:
bool operator () (const my_pair& left, const my_pair& right)
{
if (left.first < right.first)
return true;
if (left.first != right.first)
return false;
// when primary criterion is equal:
if (left.second > right.second)
// you want this to sort descending, right?
return true;
// .... other criteria
return false;
}
I think your predicate is wrong ?
If I understand correctly, you want precedence on the first element, and only in case of equality on the second element?
so maybe something like:
struct sort_pred
{
bool operator () (const my_pair& left, const my_pair& right)
{
return (left.first == right.first) ?
(left.second > right.second) :
(left.first < right.first);
}
};
Here you are
#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>
typedef std::pair<double, double> my_pair;
struct sort_pred
{
bool operator ()( const my_pair &left, const my_pair &right ) const
{
return ( left.first < right.first ) ||
( !( right.first < left.first ) && ( right.second < left.second ) );
}
};
int main()
{
std::vector<my_pair> data;
data.push_back( my_pair( 3, 2 ) );
data.push_back( my_pair( 1, 2 ) );
data.push_back( my_pair( 1, 1 ) );
data.push_back( my_pair( 2, 2 ) );
std::stable_sort( data.begin(), data.end(), sort_pred() );
for ( const auto &p : data ) std::cout << p.first << ' ' << p.second << std::endl;
}
The program output is
1 2
1 1
2 2
3 2
You want A to be before B if
A first is less than B first, or
If A first and B first are equal and B second is less than A second
In code that is
(a.first < b.first) or
((a.first == b.first) and
(b.second < a.second))
But this is suboptimal, from a programming point of view: Now you need an additional operator== implemented.
But since a value can only be less, equal or greater than another, you can rewrite that to
(a.first < b.first) or
(not (b.first > a.first) and
(b.second < a.second))

Pair equal operator overloading for inserting into set

I am trying to add a pair<int,int> to a set. If a pair shares the same two values as another in the set, it should not be inserted.
Here's my non-working code:
typedef std::pair<int, int> PairInt;
template<>
bool std::operator==(const PairInt& l, const PairInt& r)
{
return (l.first == r.first && l.second == r.second) ||
(l.first == r.second && l.second == r.first);
}
int main()
{
std::set<PairInt> intSet;
intSet.insert(PairInt(1,3));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(4,1));
}
At the moment, the (4,1) pair gets added even though there is already a (1,4) pair. The final contents of the set are:
(1 3)
(1 4)
(4 1)
and I want it to be
(1 3)
(1 4)
I've tried putting breakpoints in the overloaded method, but they never get reached. What have I done wrong?
Sets are based on operator< (an ordering/equivalence relationship), not operator== (which is an equality relationship).
To do the thing that you are trying to do, use a custom comparator:
#include <set>
#include <utility>
#include <cassert>
typedef std::pair<int, int> PairInt;
PairInt normalize(const PairInt& p) {
return p.second < p.first ? PairInt(p.second, p.first) : p;
}
struct Comparator {
bool operator()(const PairInt& l, const PairInt& r) const {
//Compare canonical forms of l and r.
return normalize(l) < normalize(r);
}
};
int main()
{
std::set<PairInt, Comparator> intSet;
intSet.insert(PairInt(1,3));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(4,1));
assert(intSet.size() == 2);
}
You will need to provide a comparison function for seeing of one item is less than the other, not for determining if they are equal. Here is a complete example:
#include <utility>
#include <algorithm>
#include <set>
#include <iostream>
typedef std::pair<int, int> PairInt;
typedef bool Compare(const PairInt &,const PairInt &);
bool compare(const PairInt &l,const PairInt &r)
{
int lfirst = std::min(l.first,l.second);
int rfirst = std::min(r.first,r.second);
if (lfirst<rfirst) return true;
if (rfirst<lfirst) return false;
return std::max(l.first,l.second)<std::max(r.first,r.second);
}
int main()
{
typedef std::set<PairInt,Compare*> IntSet;
IntSet intSet(compare);
intSet.insert(PairInt(1,3));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(1,4));
intSet.insert(PairInt(4,1));
for (IntSet::const_iterator i=intSet.begin(); i!=intSet.end(); ++i) {
std::cerr << i->first << "," << i->second << "\n";
}
}
Output:
1,3
1,4
The compare should determine if first item is less than the second item. So it should be like this:
namspace std
{
template<>
bool operator < (const PairInt& l, const PairInt& r)
{
//swap only if they're unequal to avoid infinite recursion
if (l.first != l.second)
{
//swap elements, considering your special case
if (l.first == r.second && l.second == r.first)
return l < PairInt(r.second, r.first); //call again!
}
//actual comparison is done here
if ( l.first != r.first )
return l.first < r.first;
else
return l.second < r.second;
}
}
Now it gives the desired output:
1,3
1,4
Have a look at the online demo.
Note that the compare function follows : Strict weak ordering

C++ STL Range Container

I'm looking for a container that maps from a double to object pointers. However, each key is simply a range of doubles that would correspond to that object.
For example, there could be a key/value pair that's <(0.0 3.0), ptr>, or <(3.5 10.0), ptr2>
container[1.0] should return ptr, container[3.0] should also return ptr, and container[-1.0] should be undefined.
Is there any object with similar behaviour by default or will I have to implement it myself?
Edit
Here's the actual code that I've written, it might be easier to debug/offer advice on it.
// Behavior: A range is defined mathematically as (min, max]
class dblRange
{
public:
double min;
double max;
dblRange(double min, double max)
{
this->min = min;
this->max = max;
};
dblRange(double val)
{
this->min = val;
this->max = val;
};
int compare(const dblRange rhs)
{
// 1 if this > rhs
// 0 if this == rhs
//-1 if this < rhs
if (rhs.min == rhs.max && min == max)
{
/*if (min > rhs.min)
return 1;
else if (min == rhs.min)
return 0;
else
return -1;*/
throw "You should not be comparing values like this. :(\n";
}
else if (rhs.max == rhs.min)
{
if (min > rhs.min)
return 1;
else if (min <= rhs.min && max > rhs.min)
return 0;
else // (max <= rhs.min)
return -1;
}
else if (min == max)
{
if (min >= rhs.max)
return 1;
else if (min < rhs.max && min >= rhs.min)
return 0;
else // if (min < rhs.min
return -1;
}
// Check if the two ranges are equal:
if (rhs.min == min && rhs.max == max)
{
return 0;
}
else if (rhs.min < min && rhs.max <= min)
{
// This is what happens if rhs is fully lower than this one.
return 1;
}
else if (rhs.min > min && rhs.min >= max)
{
return -1;
}
else
{
// This means there's an undefined case. Ranges are overlapping,
// so comparisons don't work quite nicely.
throw "Ranges are overlapping weirdly. :(\n";
}
};
int compare(const dblRange rhs) const
{
// 1 if this > rhs
// 0 if this == rhs
//-1 if this < rhs
if (rhs.min == rhs.max && min == max)
{
/*if (min > rhs.min)
return 1;
else if (min == rhs.min)
return 0;
else
return -1;*/
throw "You should not be comparing values like this. :(\n";
}
else if (rhs.max == rhs.min)
{
if (min > rhs.min)
return 1;
else if (min <= rhs.min && max > rhs.min)
return 0;
else // (max <= rhs.min)
return -1;
}
else if (min == max)
{
if (min >= rhs.max)
return 1;
else if (min < rhs.max && min >= rhs.min)
return 0;
else // if (min < rhs.min
return -1;
}
// Check if the two ranges are equal:
if (rhs.min == min && rhs.max == max)
{
return 0;
}
else if (rhs.min < min && rhs.max <= min)
{
// This is what happens if rhs is fully lower than this one.
return 1;
}
else if (rhs.min > min && rhs.min >= max)
{
return -1;
}
else
{
// This means there's an undefined case. Ranges are overlapping,
// so comparisons don't work quite nicely.
throw "Ranges are overlapping weirdly. :(\n";
}
};
bool operator== (const dblRange rhs ) {return (*this).compare(rhs)==0;};
bool operator== (const dblRange rhs ) const {return (*this).compare(rhs)==0;};
bool operator!= (const dblRange rhs ) {return (*this).compare(rhs)!=0;};
bool operator!= (const dblRange rhs ) const {return (*this).compare(rhs)!=0;};
bool operator< (const dblRange rhs ) {return (*this).compare(rhs)<0;};
bool operator< (const dblRange rhs ) const {return (*this).compare(rhs)<0;};
bool operator> (const dblRange rhs ) {return (*this).compare(rhs)>0;};
bool operator> (const dblRange rhs ) const {return (*this).compare(rhs)>0;};
bool operator<= (const dblRange rhs ) {return (*this).compare(rhs)<=0;};
bool operator<= (const dblRange rhs ) const {return (*this).compare(rhs)<=0;};
bool operator>= (const dblRange rhs ) {return (*this).compare(rhs)>=0;};
bool operator>= (const dblRange rhs ) const {return (*this).compare(rhs)>=0;};
};
Right now I'm having trouble having the map accept a double as a key, even though the comparison operators are defined.
Here's some driving code that I'm using to test if it would work:
std::map<dblRange, int> map;
map[dblRange(0,1)] = 1;
map[dblRange(1,4)] = 2;
map[dblRange(4,5)] = 3;
map[3.0] = 4;
I mostly agree with Earwicker in that you can define a range. Now, I am in favor of implementing operators with the real meaning (do what basic types do: two ranges compare equal if both ranges ARE equal). Then you can use the third map parameter to pass it a comparison functor (or function) that solves your particular problem with this map.
// Generic range, can be parametrized for any type (double, float, int...)
template< typename T >
class range
{
public:
typedef T value_type;
range( T const & center ) : min_( center ), max_( center ) {}
range( T const & min, T const & max )
: min_( min ), max_( max ) {}
T min() const { return min_; }
T max() const { return max_; }
private:
T min_;
T max_;
};
// Detection of outside of range to the left (smaller values):
//
// a range lhs is left (smaller) of another range if both lhs.min() and lhs.max()
// are smaller than rhs.min().
template <typename T>
struct left_of_range : public std::binary_function< range<T>, range<T>, bool >
{
bool operator()( range<T> const & lhs, range<T> const & rhs ) const
{
return lhs.min() < rhs.min()
&& lhs.max() <= rhs.min();
}
};
int main()
{
typedef std::map< range<double>, std::string, left_of_range<double> > map_type;
map_type integer; // integer part of a decimal number:
integer[ range<double>( 0.0, 1.0 ) ] = "zero";
integer[ range<double>( 1.0, 2.0 ) ] = "one";
integer[ range<double>( 2.0, 3.0 ) ] = "two";
// ...
std::cout << integer[ range<double>( 0.5 ) ] << std::endl; // zero
std::cout << integer[ range<double>( 1.0 ) ] << std::endl; // one
std::cout << integer[ 1.5 ] << std::endl; // one, again, implicit conversion kicks in
}
You must be careful with equality and comparisons among double values. Different ways of getting to the same value (in the real world) can yield slightly different floating point results.
Create a class DoubleRange to store the double range, and implement the comparison operators on it. That way, std::map will do the rest for you, with the DoubleRange class as the key.
It is better to use Interval tree data structure. Boost has an implementation in Interval Container Library
One approach would be to calculate the "break points" before hand:
typedef vector< tuple<double, double, foo*> > collisionlist_t;
const collisionlist_t vec;
vec.push_back(make_tuple(0.0, 3.0, ptr));
vec.push_back(make_tuple(3.5, 10.0, ptr2));
// sort
std::map<double, foo*> range_lower_bounds;
for(collisionlist_t::const_iterator curr(vec.begin()), end(vec.end()); curr!=end; ++curr)
{
/* if ranges are potentially overlapping, put some code here to handle it */
range_lower_bounds[curr->get<0>()] = curr->get<2>();
range_lower_bounds[curr->get<1>()] = NULL;
}
double x = // ...
std::map<double, foo*>::const_iterator citer = range_lower_bounds.lower_bound(x);
Another suggestion: Use a mathematical transform to map the index from REAL to INT which can be directly compared.
If these ranges are multiple and dense there's also a structure known as an "interval tree" which may help.
Are the intervals open or closed or half open?
I will assumed closed. Note that the intervals cannot overlap by the definition of a map. You will also need splitting rules for when one inserts an over lapping interval. the rules need to decide where the split takes place and must take into account floating point epsilon.
this implementation uses map::lower_bound and does NOT use a class as the domain of the map
map::lower_bound returns an iterator to the first element in a map with a key value that is equal to or greater than that of a specified key. (ie the least key greater than or equal to K. An unfortunate choice of STL method names as it is the least upper bound of K.)
template <class codomain>
class RangeMap : private std::map<double,std::pair<double,codomain>{
public:
typedef double domain;
typedef std::map<double,std::pair<double,codomain>:: super;
typename super::value_type value_type;
protected:
static domain& lower(const value_type& v){
return v.first;
}
static domain& upper(const value_type& v){
return v.second.first;
}
static codomain& v(const value_type& v){
return v.second.second;
}
public:
static const domain& lower(const value_type& v){
return v.first;
}
static const domain& upper(const value_type& v){
return v.second.first;
}
static const codomain& v(const value_type& v){
return v.second.second;
}
static bool is_point(const value_type& vf) {
return lower(v) == upper(v);
}
static bool is_in(const domain& d,const value_type& vf) {
return (lower(v) <= d) && (d <= upper(v));
}
const_iterator greatest_lower_bound(const domain& d)const {
const_iterator j = super::lower_bound(d);
if(j!=end() && j->first==d) return j;//d is the lh side of the closed interval
//remember j->first >= d because it was lower but its the first
if(j==begin()) return end();//d < all intervals
--j; //back up
return j;
}
const_iterator find(domain& d) {
const_iterator j = greatest_lower_bound(d);
if (is_in(j,d)) return j;
return end();
}
iterator greatest_lower_bound(const domain& d) {
iterator j = super::lower_bound(d);
if(j!=end() && j->first==d) return j;//d is the lh side of the closed interval
//remember j->first >= d because it was lower but its the first
if(j==begin()) return end();//d < all intervals
--j; //back up
return j;
}
const_iterator find(domain& d) const{
iterator j = greatest_lower_bound(d);
if (is_in(j,d)) return j;
return end();
} //so much for find(d)
iterator find(domain& d){
iterator j = greatest_lower_bound(d);
if (is_in(j,d)) return j;
return end();
} //so much for find(d)
struct overlap: public std::exception{
};
bool erase(const double lhep,const double rhep);
//you have a lot of work regarding splitting intervals erasing when overlapped
//but that can all be done with erase, and insert below.
//erase may need to split too
std::pair<iterator,bool>
split_and_or_erase_intervals(const double lhep,
const double rhep,
const codomain& cd);
//the insert method - note the addition of the overwrtite
std::pair<iterator,bool>
insert(const double lhep,const double rhep,const codomain& cd,bool overwrite_ok){
if( find(lhep)!=end() || find(rhep)!=end() ) {
if(overwrite_ok){
return split_and_or_erase_intervals(const double lhep,
const double rhep,
const codomain& cd);
}
throw overlap();
}
return insert(value_type(lhep,pair<double,codomain>(rhep,cd)));
}
};
If your intervals must be non-overlapping, you must add some extra code to verify this property at insertion-time. Specifically, the property you wish to assert is that your new interval lies entirely within a range that was previously empty. An easy way to do this is to allow two types of ranges: "occupied" and "empty". You should begin by creating a single "empty" entry which covers the entire usable range. Insertion of a new "occupied" range requires:
(1) lookup some value within the new range.
(2) ensure that the returned range is empty, and wholly encompasses your new range. (This was the required assertion, above)
(3) modify the returned empty range so its end lies at the start of your new range.
(4) insert a new empty range that begins at the end of your new range, and ends at the old end of the returned range.
(5) insert your new range, confident that it is surrounded by empty-ranges.
(6) There may be additional corner-cases when inserting a new occupied range which has no empty space separating it from other occupied ranges.