Set of custom class objects and their < operator - c++

I am trying to implement A* algorithm (with visualization in Qt). I've got this method:
result_path astar_algorithm::calculate(mapview* m_view)
{
map_view = m_view;
auto closed_set = std::vector<std::shared_ptr<node>>();
auto start_node = std::make_shared<node>(_start);
auto open_set = std::vector<std::shared_ptr<node>>{start_node};
std::map<node, node> came_from;
std::shared_ptr<node> current;
while (!open_set.empty())
{
current = *std::min_element(open_set.begin(), open_set.end());
if (*current == _end)
{
// TODO: Reconstruct a result path!!!
break;
}
open_set.erase(std::find(open_set.begin(), open_set.end(), current));
closed_set.push_back(current);
auto neighbors = get_neighbors(*current);
for (auto& neighbor : neighbors)
{
if (std::find_if(closed_set.begin(), closed_set.end(),
[&](std::shared_ptr<node> const& p) { return *p == neighbor; }) !=
closed_set.end())
continue;
auto tentative_g_score = current->G + 1;
if (std::find_if(open_set.begin(), open_set.end(), [&](std::shared_ptr<node> const& p) {
return *p == neighbor;
}) == open_set.end())
{
neighbor.G = tentative_g_score;
neighbor.H = heuristic_cost_estimate(neighbor.pos, _end);
neighbor.parent = current;
open_set.push_back(std::make_shared<node>(neighbor));
}
else if (tentative_g_score < neighbor.G)
{
neighbor.parent = current;
neighbor.G = tentative_g_score;
}
}
}
auto result = result_path();
while (*current != *start_node)
{
result.path.push_back(current->pos);
current = current->parent;
}
result.path.push_back(start_node.pos);
std::reverse(result.path.begin(), result.path.end());
return result;
}
It works, but I have a few problems:
if (std::find_if(closed_set.begin(), closed_set.end(),
[&](std::shared_ptr<node> const& p) { return *p == neighbor; }) !=
closed_set.end())
continue;
This line checks if a node is present in an std::vector and if so, it continues the loop (then there is a second line similar to this, it just checks if node is not actually present in the vector). I guess the better way would be to store those nodes in a vector and then searching and further adding would be easier (cuz I just have to check if the insert succeeded).
The problem is, afaik, to make this work I have to implement < operator. And so I did. I also made == and !=:
class node
{
public:
node() {}
node(const QPoint& p) : pos(p) {}
bool operator == (const node& o ) const { return pos == o.pos; }
bool operator == (const QPoint& o ) const { return pos == o; }
bool operator != (const node& o) const {return pos != o.pos; }
bool operator <(const node& o ) const { return G + H < o.G + o.H; }
QPoint pos;
std::shared_ptr<node> parent;
int G = 0;
int H = 0;
};
It works perfectly for the earlier search for std::min_element (it searches for a node with the lowest F value (F=G+H)), it uses < operator. But then I tried to use a set, so those two vectors at the beginning of the method were set and when I wanted to just insert or even check if a node is already in a set and then insert I had a problem. Many of those nodes will have the same G+H value, as the maze which I used was kind of simple (i.e. a maze completely without terrains). I checked it under the debugger and the nodes with unique .pos values (QPoint) were not added to the set just like they weren't unique (but if the node had a different G+H value than any node in the set, it would be added). For the vector the same nodes of course work, cuz there are no checks made, I checked everything carefully under the debugger.
I don't know if I am getting this wrong, but I thought it would use a == or != operators but as seen in this answer: link, it actually uses < operator, which in my case would not distinguish between two nodes (cuz the unique part of each node is its position in the grid (node represents a box in a grid, which can represent a maze or smth similar))
So, is there something I am doing wrong or am I actually getting this right, and the inserting (which checks if the element is unique) or checking if the element exists in a set uses < operator and I cannot do anything about it? (cuz I would like to have my < operator with comparing G+H and then I would like the searching/inserting to use the == operator to compare)
This is the example that I wrote (I forgot I have Microsoft C++ Compiler from the command line - cl.exe)
#include <algorithm>
#include <iostream>
#include <memory>
#include <set>
class Point
{
public:
int _x, _y;
Point() : _x(0), _y(0) {}
Point(int x, int y) : _x(x), _y(y) {}
bool operator==(const Point& p) const { return _x == p._x && _y == p._y; }
bool operator!=(const Point& p) const { return _x != p._x && _y != p._y; }
};
class node
{
public:
node() {}
node(const Point& p) : pos(p) {}
bool operator==(const node& o) const { return pos == o.pos; }
bool operator==(const Point& o) const { return pos == o; }
bool operator!=(const node& o) const { return pos != o.pos; }
bool operator<(const node& o) const { return G + H < o.G + o.H; }
Point pos;
std::shared_ptr<node> parent;
int G = 0;
int H = 0;
};
int main()
{
node n1(Point(0, 0));
n1.G = 1;
n1.H = 1;
node n2(Point(1, 1));
n2.G = 2;
n2.H = 2;
node n3(Point(2, 2));
n3.G = 1;
n3.H = 1;
std::set<node> nodes;
nodes.insert(n1);
nodes.insert(n2);
nodes.insert(n3);
auto min = (*std::min_element(nodes.begin(), nodes.end())).pos;
std::cout << min._x << " " << min._y << '\n';
std::cout << nodes.size() << '\n';
}
>main.exe
0 0
2
std::min_element works, but those are 3 unique nodes for me (differet .pos values) so there should be 3 nodes in the set. And that's what I want to achieve

I thought it would use a == or != operators
No, std::set does not use operators == and !=,
std::set uses just one function, the comparison function (the second template argument, which defaults to std::less<T>).
Uniqueness is based on the equivalence relation which is derived from applying the same comparison function twice: !a<b && !b<a.
It seems you don't really need uniqueness, in which case you can use std::multiset instead. It will maintain the order, but will not enforce uniqueness.
std::set<node> nodes;
. . .
auto min = (*std::min_element(nodes.begin(), nodes.end())).pos;
std::min_element is always O(N). Using it on a set defeats the purpose of having a set.
Just get the first element, which will be the smallest (according to the comparison function).
auto min = begin(nodes)->pos;

Related

Using `std::equal_range` with `boost::transform_iterator`

Say I have a struct of Items that I'm storing in an std::set and sorting like so:
struct Position
{
int x;
int y;
}
struct Item
{
std::string id;
Position position;
// NOTE: only `position` should matter for equality
operator==(const Item& other)
{
return position == position;
}
};
inline bool operator<(const Item& lhs, const Item& rhs)
{
if (lhs.position.x == rhs.position.x)
{
return lhs.position.y < rhs.position.y;
}
return lhs.position.x < rhs.position.x;
}
using ItemSet = std::set<Item>;
I want to use std::equal_range to search an ItemSet, except I want to search by Position. I know I could do something like:
ItemSet items;
Item tempItem;
tempItem.position = some_position;
auto result = std::equal_range(items.begin(), items.end(), tempItem);
But I would like to avoid the temporary Item.
I tried to use boost::transform_terator like so:
auto tr = [](const Item& item) { return item.pos; };
auto tr_begin = boost::make_transform_iterator(items.begin(), tr);
auto tr_end = boost::make_transform_iterator(items.end(), tr);
Position findme { 2, 1 };
auto result = std::equal_range(tr_begin, tr_end, findme);
But this doesn't compile for reasons I don't understand, and also even if it did work, how would I get an iterator into the original collection from result? Or maybe there's a better way overall to do this?
Here is a test harness show the problem: http://cpp.sh/3hzsq
Any help would be appreciated!
You can use std::set::find with a different type to avoid constructing an Item. Note that your set can only contain one item with a specific position.
You can either make Position directly comparable with Item (Add Item{} < Position{} and Position{} < Item{}) or create a new proxy class:
struct ItemPosition {
Position p;
};
inline bool operator<(const ItemPosition& l, const Item& r) {
return l.position.x == r.position.x ? l.position.y < r.position.y : l.position.x < r.position.x;
};
inline bool operator<(const Item& l, const ItemPosition& r) {
return l.position.x == r.position.x ? l.position.y < r.position.y : l.position.x < r.position.x;
};
// Change the comparator so it can compare with `ItemPosition` too
using ItemSet = std::set<Item, std::less<>>;
You can alternatively use a completely different comparator to make Position comparable with Item.
struct ItemComparator {
bool operator()(const Position& l, const Position& r) const {
return l.x == r.x ? l.y < r.y : l.x < r.x;
}
bool operator()(const Item& l, const Item& r) const {
return operator()(l.position, r.position);
}
bool operator()(const Item& l, const Position& r) const {
return operator()(l.position, r);
}
bool operator()(const Position& l, const Item& r) const {
return operator()(l, r.position);
}
using is_transparent = void;
};
using ItemSet = std::set<Item, ItemComparator>;
And use it like so:
Position findme { 2, 1 };
// Or just `items.find(findme)` if using a custom comparator
auto result = items.find(ItemPosition{ findme });
if (result == items.end()) {
// No item found
} else {
Item& item = *result;
// found item
}

How do I compare strings (insensitive case), while inserting same strings into a binary Tree (sensitive case)?

Essentially, I am a brick wall as to how I should compare strings with my insert function, not taking case into consideration while simultaneously inserting those same strings with their original case.
Here is my insert function.
TreeNode* Tree::insert(TreeNode* node, string value) {
transform(value.begin(), value.end(), value.begin(), ::tolower);
if (node == nullptr) {
return newTreeNode(value);
}
if (node->data < value) {
node->left = insert(node->left, value);
}
else if(node-> data > value) {
node->right = insert(node->right, value);
}
else {
return node;
}
node->height = 1 + max(height(node->left), height(node->right));
return node;
}
Here is my tree header file:
struct TreeNode {
public:
TreeNode* left;
TreeNode* right;
string data;
};
class Tree {
public:
TreeNode * newTreeNode(string data);
TreeNode * insert(TreeNode* node, string value);
void lexographicPrint(TreeNode* root);
};
newTreeNode Funciton:
TreeNode* AvlTree::newTreeNode(string value) {
TreeNode* treeNode = new TreeNode();
treeNode->data = value;
treeNode->left = nullptr;
treeNode->right= nullptr;
treeNode->height = 1;
return treeNode;
}
Print Function:
void AvlTree::lexographicPrint(TreeNode* root) {
if (root != nullptr) {
lexographicPrint(root->right);
cout << root->data << " ";
lexographicPrint(root->left);
}
}
This currently works as I want it to except for the fact that the Tree contains all values as lower case, obviously due to the transform function. I have tried using a holdValue, like so:
string holdValue;
if (isupper(value[0]) {
holdValue = value;
}
at the top of my function, replacing all insert calls with holdValue. I am confused as to why that changes the order of my tree when comparisons are still made with value. I expected that to work, but it does not. I have yet to find a solution through Google searches.
Rather than use std::string's <, you can use a case insensitive comparison.
bool ci_less(const std::string & lhs, const std::string & rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char l, char r){ return std::to_lower(l) < std::tolower(r); });
}
TreeNode* Tree::insert(TreeNode* node, std::string value) {
if (node == nullptr) {
return newTreeNode(std::move(value));
}
if (ci_less(node->data, value)) {
node->left = insert(node->left, std::move(value));
}
else if(ci_less(value, node->data)) {
node->right = insert(node->right, std::move(value));
}
else {
return node;
}
node->height = 1 + max(height(node->left), height(node->right));
return node;
}
You will need to #include <algorithm> for std::lexicographical_compare.
In a similar vein, you could instead define a case insensitive string type
struct ci_char_traits : public std::char_traits<char> {
static char to_upper(char ch) {
return std::toupper((unsigned char) ch);
}
static bool eq(char c1, char c2) {
return to_upper(c1) == to_upper(c2);
}
static bool lt(char c1, char c2) {
return to_upper(c1) < to_upper(c2);
}
static int compare(const char* s1, const char* s2, size_t n) {
while ( n-- != 0 ) {
if ( to_upper(*s1) < to_upper(*s2) ) return -1;
if ( to_upper(*s1) > to_upper(*s2) ) return 1;
++s1; ++s2;
}
return 0;
}
static const char* find(const char* s, int n, char a) {
auto const ua (to_upper(a));
while ( n-- != 0 )
{
if (to_upper(*s) == ua)
return s;
s++;
}
return nullptr;
}
};
using ci_string = std::basic_string<char, ci_char_traits>;
Essentially, you want to store mixed-case values, but ordered as if they were lowercase.
There are two things you can do.
Replace all of your a < b and a > b checks with case_insensitive_compare(a, b) < 0 and case_insensitive_compare(a, b) > 0, where case_insensitive_compare looks something like:
// +ve => l > r
// 0 => l equivalent to r (possibly different case)
// -ve => l < r
int case_insensitive_compare(const std::string& l, const std::string& r) noexcept {
std::size_t max_size = std::max(l.size(), r.size());
for (std::size_t i = 0; i < max_size; ++i) {
int cmp = std::tolower(l[i]) - std::tolower(r[i]);
if (cmp != 0) return cmp;
}
return l.size() - r.size();
}
// Or in c++20
// std::weak_ordering::greater => l > r
// std::weak_ordering::equivalent => l equivalent to r
// std::weak_ordering::less => l < r
std::weak_ordering case_insensitive_compare(const std::string& l, const std::string& r) noexcept {
std::size_t max_size = std::max(l.size(), r.size());
for (std::size_t i = 0; i < max_size; ++i) {
auto cmp = std::tolower(l[i]) <=> std::tolower(r[i]);
if (cmp != 0) return cmp;
}
return l.size() <=> r.size();
}
You should be able to generalise this to any comparator function (For a Tree<T>, a comparator cmp(const T&, const T&) -> int)
Make your TreeNode store a key/value pair, where the key is the lowercase string, and the value is the mixed-case string. If you need to make the tree store another value, make the value a std::tuple<std::string, ValueType>.

List iterators incompatible and Arithmetic overflow

I have such part of the code:
for (auto i = 0; i < WINDOW_SIZE; ++i) {
if (current == goal) break;
if (came_from.find(current) == came_from.end())
break;
next_best = came_from[current];
if (space_map[i].find(next_best) != space_map[i + 1].end()) {
auto search1 = space_map[i].find(current);
... ... ... ... ... ...
Where
1) space_map: std::vector<std::unordered_map<Node, Agent*>> (where Node and Agent are my onw classes.)
2) came_from: std::unordered_map<Node, Node>
3) current, goal: Node
4) WINDOW_SIZE: define, which equal 8
There is listing of hash_map specialization for the unordered_map, and Node:
namespace std {
template <>
struct hash<Node> {
std::size_t operator()(const Node &n) const {
std::size_t const h1 = std::hash<std::size_t>{}(n.x);
std::size_t const h2 = std::hash<std::size_t>{}(n.y);
return (h1 ^ (h2 << 1));
}
};
} // namespace std
typedef struct Node {
uint x, y, f_score, g_score;
bool operator==(const Node &n) const { return (x == n.x && y == n.y); }
bool operator!=(const Node &n) const { return (x != n.x || y != n.y); }
friend std::ostream &operator<<(std::ostream &out, const Node &n) {
out << '[' << n.x << ", " << n.y << ']';
return (out);
}
} Node;
When I'm starting the Debuging, I receive such error:
I considered the case with WINDOW_SIZE - 1, probably it was an overflow error. But not sill the same error.
I figgured out that an error occurs here:
if (space_map[i].find(next_best) != space_map[i + 1].end())
Also I have such notification, that I have a "C26451: Arithmetic overflow" with "+" here space_map[i + 1].end() [i + 1]. Probably it will helpful.
Could someone explain me, where am I wrong?
You didn't give a Minimal, complete verifiable example
But space_map[i].find(next_best) != space_map[i + 1].end()
Is not possible. std::unordered_map::find() returns an iterator. And:
Return value
Iterator to an element with key equivalent to key. If no such element is found, past-the-end (see end()) iterator is returned.
But the iterator of space_map[i] is different from that of space_map[i+1]. You cannot compare two different iterators
So it either should be
space_map[i].find(next_best) != space_map[i].end()
or
space_map[i+1].find(next_best) != space_map[i+1].end()
You don't show enough code, so I have to make assumptions here. I would use a a local reference, to prevent making this mistake.
for (auto spMpCurrent = std::cbegin(space_map);
spMpCurrent != std::next(std::cbegin(space_map), WINDOW_SIZE);
spMpCurrent++) {
if (current == goal) break;
if (came_from.find(current) == came_from.end()) break;
next_best = came_from[current]; // inline this?
auto spMpNext = std::next(spMpCurrent);
if (spMpNext->find(next_best) != spMpNext->end()) {
auto search1 = spMpCurrent->find(current);
Your design looks very (unnecessarily?) complex. Maybe it can be simplified... but as you don't show enough code, we cannot help you

C++ multiset with struct erase only one, fast (overload?)

struct node {
int idx; // each node have a unique index
int value; // different nodes can have same value
}
struct node_help {
bool operator()(const node &a, const node &b) const
{
return a.value < b.value;
}
}
std::multiset<node, node_help> Nodes;
So far so good. Now I want to erase a specific node from the multiset. When I use this:
Nodes.erase(node (x, y));
Every node with the value y gets removed but I want only the node to be removed with the value y and index x.
I solved this by doing it manually:
for (std::multiset<node, node_comp>::iterator iter = Nodes.begin(); iter != Nodes.end(); iter++) {
node actual_node = *iter;
if (actual_node.idx == to_delete.idx && actual_node.value == to_delete.value) {
Nodes.erase(iter);
}
return;
}
But this seems to have a bad performance.
I have millions of nodes to I need every speedup I can get. :)
Any ideas?
OK all fine. I've just forgot to take the lines with the log off. So after every change in the set, a log was created and saved directly to the disk. Commenting this out reduced the time from 50 seconds to 0,0x seconds. Fast enough. :)
But ty anyways for all the responses.
struct node_help {
bool operator()(const node &a, const node &b) const
{
return a.value <= b.value; // changes the equality check
}
}
If you have C++11 you can define a lambda as follows and make it a better code. Otherwise you may need to use function object in place of lambda.
std::remove(std::begin(Nodes), std::end(Nodes),
[&to_delete]( struct node & x )
{
return ( x.idx == to_delete.idx &&
x.value == to_delete.value);
}
Please note, to use std::remove() you have to incclude algorithm header file:
#include<algorithm>
You can somehow obtain an iterator to an element which is equal to node (x, y). Then, a erase with an iterator will remove only one element.
That said, with such comparison function, you will have some trouble (in terms of complexity) finding node (x, y) anyway, since nodes with the same y and different x are not efficiently searchable by x.
One solution is to change the comparison function to order by y and then by x, and perhaps use a set instead of a multiset then.
After that, if you want an element with a specific y, you can use something like Nodes.lower_bound (node (-INFINITY, y)) which has complexity O (log n).
how about
std::multiset<node, node_help> Nodes;
Nodes.insert(node{ 1, 10 });
Nodes.insert(node{ 2, 1 });
Nodes.insert(node{ 3, 1 });
Nodes.insert(node{ 4, 100 });
cout << "before\n";
for (const auto& n : Nodes)
{
cout << n.idx << " " << n.value << "\n";
}
auto it = Nodes.find(node{ 0,1 });
while(it != Nodes.end())
{
Nodes.erase(it);
it = Nodes.find(node{ 0,1 });
}
cout << "\nafter\n";
for (const auto& n : Nodes)
{
cout << n.idx << " " << n.value << "\n";
}
You can get a constant factor speedup by only looking at the nodes where y matches
auto [first, last] = Nodes.equal_range(to_delete);
auto it = std::find(first, last, to_delete);
if (it != last) {
Nodes.erase(it);
}
Or without C++17
auto range = Nodes.equal_range(to_delete);
auto it = std::find(range.first, range.second, to_delete);
if (it != range.second) {
Nodes.erase(it);
}
With C++14 you could have Nodes searchable by either node or int
struct node {
int idx; // each node have a unique index
int value; // different nodes can have same value
};
struct node_less {
using is_transparent = void;
bool operator()(const node &a, const node &b) const
{
return std::tie(a.value, a.key) < std::tie(b.value, b.key);
}
bool operator()(int a, const node &b) const
{
return a < b.value;
}
bool operator()(const node &a, int b) const
{
return a.value < b;
}
};
std::set<node, node_less> Nodes;
Nodes.find(node{ x, y }); // single node
Nodes.equal_range(y); // all nodes that match y

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.