Я создаю std::map
, используя ключи, которые содержат, например. double
в качестве идентификаторов. Однако мне нужно, чтобы ключи имели некоторую свободу действий, то есть допуск, чтобы идентифицировать ключ как «одинаковый».
Мне удалось это реализовать, определив операторы <
и ==
самостоятельно.
Сейчас я нахожусь в следующей ситуации: допустим, я добавляю элемент на свою карту, используя какой-то ключ a
. Теперь я могу получить значения, хранящиеся с этим ключом, используя некоторый ключ b
, идентификационный двойник которого немного отличается от a
. Это все, как задумано. Однако сейчас я хочу сравнить разницу между клавишами a
и b
. Для этого я ищу способ получить a
с карты.
Есть ли способ сделать это без перебора всей карты в поисках первого пройденного элемента a == b
?
Вот пример кода того, что я пытаюсь сделать:
#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;
}
Обновлено:
Добавление фрагмента кода моего решения после ответа HolyBlackCat:
#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;
}
🤔 А знаете ли вы, что...
C++ обладает богатой стандартной библиотекой, которая включает контейнеры, алгоритмы и многое другое.
То, что вы делаете, изначально не законно. Компаратор карт должен реализовывать строгий слабый порядок, а ваш нет, потому что, например. ваша эквивалентность (то есть !(a < b) && !(b < a)
) не транзитивна: 0 ≡ 0.4
и 0.4 ≡ 0.8
, но 0 ≢ 0.8
.
Правильный способ сделать это:
<
,==
возвращать точный результат.std::less<>
(без типа аргумента, он же std::less<void>
) (если вы перегрузили <
для сравнения двух разных типов ключей), либо свой собственный, поддерживающий оба типа..find()
или .equal_range()
.Затем вы можете проверить .first
в возвращенных итераторах, чтобы увидеть исходный ключ.
Вам необходимо определить сравнение для ключей, учитывающих допуск, который вы использовали для определения типа карты:
constexpr auto tol = 0.5;
// comparision of keys
struct dbl_comp_with_tol
{
bool operator()(double v1, double v2) const { return v1 - v2 < -tol; };
};
// map double(with tol) -> std::string
std::map<double, std::string, dbl_comp_with_tol> the_map;
Затем, когда вы найдете элемент на карте для данного ключа, вы можете сравнить ключи карты с тем, который вы использовали для поиска элемента:
// look for a value associated with b, and report distance to its key:
auto i_b = the_map.find(b);
if (i_b != the_map.end())
{
std::cout << "I found a value for " << b << " set against " << i_b->first << ": " << i_b->second << ".\n"
<< "The difference between the keys was " << (i_b->first - b) << std::endl;
}
else
{
std::cout << "I didn't find a value for " << b << std::endl;
}
Все в одном примере: https://godbolt.org/z/z3ca6ddcP
map
требует строгого слабого порядка. Если вы попытаетесь добавить «допуск» таким образом, это не будет строгим слабым упорядочением, и в результате map
будет «случайно» создавать новые узлы, когда они должны были быть объединены, и «случайно» объединять узлы, которые этого не должно было быть, и он случайно запустит неправильный код, удалив вашу папку Documents
. Упс. Не делай этого.
Однако не вся надежда потеряна! Самое простое решение — создать класс fuzzy_map
, содержащий член std::multi_map<mostly_unique_key, data>
. Тогда ваш класс будет иметь метод, который линейно перебирает узлы с этим mostly_unique_key
и проверяет узлы в пределах вашего допуска и либо возвращает это data
, либо создает новые data
, или что-то еще, что вы хотите. Обратите внимание: поскольку допуск не является транзитивным, велика вероятность, что вы случайно напишете код, объединяющий узлы, которые не должны были быть объединены, или не сумеете объединить узлы, которые должны были быть объединены, поэтому вам придется очень хорошо подумать о том, что именно вам нужно.
Если у вас нет каких-то нечетких mostly_unique_key
, то это еще решаемо. Вы заставляете свой класс содержать std::map<double, data>
, а затем ваш метод будет линейно выполнять итерацию от map.lower_bound(x-tolarance)
до map.upper_bound(x+tolarance)
, а затем снова самостоятельно выяснять логику слияния.
Возвращаясь к заданному вами вопросу, std::map::lower_bound
, std::map::upper_bound
и std::map::find
возвращают итераторы к std::pair<key, value>
, что позволяет вам изучить оригинал key
, использованный во вставке. Вы не можете изменить ключ напрямую, но можете прочитать его, а затем при необходимости удалить или повторно вставить весь узел.