c++c++17stdmap

C++ map: How do I get the key that was used to add an element into a std::map?


I'm creating a std::map using keys that contain e.g. doubles 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;
}

Solution

  • 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;

    Then you can examine .first in the returned iterators to see the original key.