Ассоциативная карта Key to Key в C++

Я ищу что-то вроде карты «Ключ к ключу» на С++.

Мое намерение заключается в следующем:

  • Каждый ключ - как на "левой", так и на "правой" стороне уникален
  • Ключ левой стороны можно искать по ключу правой стороны и наоборот

В качестве примера и чтобы сделать мое намерение более ясным, в коде это, вероятно, будет выглядеть так:

key2key<int, string> myMap; // int maps to string, string to int

myMap.insert(0, "zero");
myMap.insert(1, "one");
myMap.insert(2, "two");

myMap.insert(1, "zero"); // would throw an error
myMap.insert(9, "one"); // would throw an error as well

cout << myMap.lookupLeft(1) << endl; // prints "one"
cout << myMap.lookupRight("one") << endl; // prints "1"

Конечно, я мог бы пойти дальше и реализовать что-то подобное самостоятельно, но существует ли что-нибудь там? Я не хочу изобретать велосипед, поэтому, возможно, можно модифицировать или повторно использовать стандартные контейнеры STL или повышать их.

Почему я считаю, что это полезно?

Представьте, что вы читаете файл конфигурации, и вы также хотите написать или внести изменения в этот файл конфигурации. Этот файл конфигурации может содержать некоторые поля, которые внутренне в С++ представлены как безопасные классы перечисления. Использование карты «Ключ к ключу» — это очень легкий генератор и преобразователь типов этих значений.

enum class DebugLevel {error, warning, debug};
const key2key<DebugLevel, string> debugLevelMap = {
  {DebugLevel::error, "error"},
  {DebugLevel::warning, "warning"},
  {DebugLevel::debug, "debug"},
}

DebugLevel foo = debugLevelMap.lookupRight("error");
string bar = debugLevelMap.lookupLeft(DebugLevel::warning);

person rralf    schedule 16.02.2015    source источник
comment
Вы можете просто использовать две карты. Если объекты большие, рассмотрите возможность размещения их в другом месте (например, в векторе) и сохранения только указателей на карте. Или используйте общие указатели.   -  person 5gon12eder    schedule 17.02.2015
comment
Но это не мешает тому, что одна карта потенциально может содержать два эквивалентных значения.   -  person rralf    schedule 17.02.2015
comment
Вам нужно будет убедиться, что ни одна из карт уже не содержит ключ/значение, прежде чем вставлять в любую из них.   -  person 5gon12eder    schedule 17.02.2015
comment
Взгляните на boost.bimap.   -  person Pradhan    schedule 17.02.2015
comment
boost::bimap   -  person James Adkison    schedule 17.02.2015
comment
@Pradhan Я думаю, это именно то, что я искал!   -  person rralf    schedule 17.02.2015
comment
О нет, он не поддерживает эти элегантные списки инициализаторов...   -  person rralf    schedule 17.02.2015


Ответы (2)


Когда я публиковал свой первый ответ, я не знал, что такая вещь, как boost::bimap существует. Я согласен с тем, что создание собственной двунаправленной карты, вероятно, уступает использованию предположительно очень качественной реализации Boost. Тем более, если ваш проект уже зависит от Boost. Если вас больше всего беспокоит отсутствие конструктора списка инициализаторов для boost::bimap, вы можете легко добавить эту функциональность в качестве фабричной функции.

#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>

#include <boost/bimap.hpp>

template<typename T1, typename T2>
boost::bimap<T1, T2>
make_bimap(const std::initializer_list<std::pair<T1, T2>> initlist)
{
  using bimap_type = boost::bimap<T1, T2>;
  using value_type = typename bimap_type::value_type;
  bimap_type bimap {};
  for (const auto& iter : initlist)
    {
      if (!bimap.insert(value_type {iter.first, iter.second}).second)
        throw std::invalid_argument {"already mapped"};
    }
  return bimap;
}

int
main()
{
  using namespace std::literals;
  const auto bimap = make_bimap<int, std::string>({
      {0, "zero"s},
      {1, "one"s},
      {2, "two"s},
   // {1, "zero"s},  // would throw
   // {9, "one"s},   // would throw
  });
  std::cout << bimap.left.at(1) << std::endl;
  std::cout << bimap.right.at("one") << std::endl;
  return 0;
}

Выход:

one
1

Благодарность за первоначальное упоминание boost::bimap принадлежит @Pradhan.

person 5gon12eder    schedule 16.02.2015
comment
об обнаружении дубликатов, нет никакой разницы в семантике std::map. Просто вызовите insert/emplace и проверьте возвращаемое значение. - person sehe; 17.02.2015
comment
@sehe Спасибо, я предполагал, что это должно быть легко возможно, но не смог выяснить, как это сделать во вчерашней спешке. Обновил ответ в соответствии с вашим предложением. - person 5gon12eder; 17.02.2015

Вы можете легко реализовать это, используя два std::map.

#include <iostream>
#include <map>
#include <stdexcept>
#include <string>

template<typename T1, typename T2>
class BidirectionalMap
{

private:

  std::map<T1, T2> lhs2rhs_ {};
  std::map<T2, T1> rhs2lhs_ {};

public:

  BidirectionalMap()
  {
  }

  // This is not thread-safe, if you need thread-safety, use a mutex.
  void
  insert(const T1& lhs, const T2& rhs)
  {
    if (this->lhs2rhs_.count(lhs) || this->rhs2lhs_.count(rhs))
      throw std::invalid_argument {"already mapped"};
    this->lhs2rhs_[lhs] = rhs;
    this->rhs2lhs_[rhs] = lhs;
  }

  T2
  lookupLeft(const T1& lhs) const
  {
    return this->lhs2rhs_.at(lhs);
  }

  T1
  lookupRight(const T2& rhs) const
  {
    return this->rhs2lhs_.at(rhs);
  }
};

template<typename T1, typename T2>
void
demo_insert(BidirectionalMap<T1, T2>& mymap, const T1& lhs, const T2& rhs)
{
  try
    {
      mymap.insert(lhs, rhs);
    }
  catch (const std::exception& e)
    {
      std::cerr << "cannot insert (" << lhs << ", " << rhs << "): "
                << e.what() << std::endl;
    }
}


int
main()
{
  using namespace std::literals;
  BidirectionalMap<int, std::string> mymap {};
  demo_insert(mymap, 0, "zero"s);
  demo_insert(mymap, 1, "one"s);
  demo_insert(mymap, 2, "two"s);
  demo_insert(mymap, 1, "zero"s);
  demo_insert(mymap, 9, "one"s);
  std::cout << mymap.lookupLeft(1) << std::endl;
  std::cout << mymap.lookupRight("one") << std::endl;
  return 0;
}

Выход:

cannot insert (1, zero): already mapped
cannot insert (9, one): already mapped
one
1
person 5gon12eder    schedule 16.02.2015
comment
Сначала я не был убежден в этом решении, так как я не люблю варить свой собственный суп для тривиальных проблем, но, возможно, это действительно самый простой способ реализовать это. Это решение также можно легко расширить с помощью списков инициализаторов. Дайте мне поспать на ней одну ночь. - person rralf; 17.02.2015
comment
@rralf Если отсутствие конструктора списка инициализаторов является вашим основным возражением, посмотрите мой другой ответ. - person 5gon12eder; 17.02.2015