I'm creating a std::map
using keys that contain e.g. double
s as identifiers. However, I need the keys to have some leeway, i.e. tolerance, to identify a key as "the same".
I've successfully managed to implement that by defining the operators <
and ==
myself.
I'm now in the following situation: Say I add an element to my map using some key a
. I can now retrieve the values stored with that key using some key b
, whose identifying double is slightly off from a
's. That's all as intended. However, what I want now is to compare the difference between the keys a
and b
. To do so, I'm looking for a way to retrieve a
from the map.
Is there any way of doing so without iterating over the entire map to find the first element that passes a == b
?
Here's example code of what I'm trying to do:
#include <iostream>
#include <map>
class myKey {
public:
myKey(double x, double tolerance=0.5);
double getX() const;
bool operator<(const myKey& rhs) const;
bool operator==(const myKey& rhs) const;
private:
double _x;
double _tolerance;
};
myKey::myKey(double x, double tolerance){
_x = x;
_tolerance = tolerance;
}
double myKey::getX() const {
return _x;
}
bool myKey::operator<(const myKey& rhs) const {
return (rhs._x - _x) > _tolerance;
}
bool myKey::operator==(const myKey& rhs) const {
return std::abs(_x - rhs._x) < _tolerance;
}
// --------------------------------------------------
int main() {
std::map<myKey, double> myMap;
// create a key and assign it a value in the map
myKey a = myKey(1.234, 0.5);
double aval = 3.14159;
myMap[a] = aval;
double value_of_a_in_map = myMap[a];
std::cout << "Checking myMap[a]: ";
std::cout << "same value? " <<
(aval == value_of_a_in_map) << std::endl;
// Get a new key that should pass a == b
// since it's within the tolerance of 0.5
myKey b = myKey(1.345, 0.5);
std::cout << "a == b? " << (a == b) << std::endl;
// retrieve value of `a` from map using `b`
double value_of_a_in_map_using_b = myMap[b];
std::cout << "Checking myMap[b]: ";
std::cout << "same value? " <<
(aval == value_of_a_in_map_using_b) << std::endl;
// How do I get original value(s) of `myKey a` from the map?
// Can I do it without iterating over the entire map?
// This works
for (auto entry: myMap){
myKey mapKey = entry.first;
if (mapKey == b) {
std::cout << "Found a ! x= " << mapKey.getX() << std::endl;
}
}
return 0;
}
EDIT:
Adding code snippet of my solution following HolyBlackCat's answer:
#include <iostream>
#include <map>
#include <cassert>
class myIDKey {
public:
myIDKey(double x);
myIDKey(){};
double getX() const;
private:
double _x;
};
myIDKey::myIDKey(double x){
_x = x;
}
double myIDKey::getX() const {
return _x;
}
bool operator<(const myIDKey& lhs, const myIDKey& rhs) {
return lhs.getX() < rhs.getX();
}
bool operator==(const myIDKey& lhs, const myIDKey& rhs) {
return lhs.getX() == rhs.getX();
}
class mySearchKey : public myIDKey {
public:
mySearchKey(double x, double tolerance = 0.5);
mySearchKey(){};
double getTolerance() const;
private:
double _tolerance;
};
mySearchKey::mySearchKey(double x, double tolerance): myIDKey::myIDKey(x) {
_tolerance = tolerance;
}
double mySearchKey::getTolerance() const {
return _tolerance;
}
bool operator<(const mySearchKey& lhs, const mySearchKey& rhs) {
return lhs.getX() < rhs.getX();
}
bool operator==(const mySearchKey& lhs, const mySearchKey& rhs) {
return lhs.getX() == rhs.getX();
}
bool operator<(const mySearchKey& lhs, const myIDKey& rhs) {
return (lhs.getX() - rhs.getX()) < -lhs.getTolerance();
}
bool operator==(const mySearchKey& lhs, const myIDKey& rhs) {
return std::abs(rhs.getX() - lhs.getX()) < lhs.getTolerance();
}
bool operator<(const myIDKey& lhs, const mySearchKey& rhs) {
return (lhs.getX() - rhs.getX()) < -rhs.getTolerance();
}
bool operator==(const myIDKey& lhs, const mySearchKey& rhs) {
return std::abs(rhs.getX() - lhs.getX()) < rhs.getTolerance();
}
// --------------------------------------------------
int main() {
// tell the map to use std::less<> as comparator instead
// of std::less<key>. std::less<> invokes the `<` operator.
// We want that to be able to use our bespoke `<` operator
// for different search and ID keys.
std::map<myIDKey, double, std::less<>> myMap;
myIDKey keyA(1.);
myMap[keyA] = 1.;
myIDKey keyB(2.);
myMap[keyB] = 2.;
mySearchKey searchKeyA = mySearchKey(1., 0.5);
mySearchKey searchKeyA2 = mySearchKey(1.2, 0.5);
auto search1 = myMap.find(searchKeyA);
if (search1 != myMap.end()) {
assert(search1->first.getX() == keyA.getX());
std::cout << "Search 1 successful" << std::endl;
} else {
std::cout << "Search 1 failed" << std::endl;
}
auto search2 = myMap.find(searchKeyA2);
if (search2 != myMap.end()) {
assert(search2->first.getX() == keyA.getX());
assert(search2->first.getX() != searchKeyA2.getX());
std::cout << "Search 2 successful" << std::endl;
} else {
std::cout << "Search 2 failed" << std::endl;
}
std::cout << "Done." << std::endl;
return 0;
}
What you're doing is not legal in the first place. The map comparator must implement strict weak ordering, and yours doesn't because e.g. your equivalence (that is, !(a < b) && !(b < a)
) is not transitive: 0 ≡ 0.4
and 0.4 ≡ 0.8
, but 0 ≢ 0.8
.
The correct way of doing this is;
<
,==
return the exact result.std::less<>
(with no argument type, aka std::less<void>
) (if you overloaded <
to compare the two different key types), or your own that supports both types..find()
or with .equal_range()
.Then you can examine .first
in the returned iterators to see the original key.