Асоциативна карта на ключ към ключ в C++

Търся нещо като карта "Ключ към ключ" в 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 контейнери или да увелича.

Защо смятам, че е полезно?

Представете си, че четете конфигурационен файл и също искате да напишете или направите промени в този конфигурационен файл. Този конфигурационен файл може да съдържа някои полета, които вътрешно в c++ са представени като безопасни тип enum класове. Използването на карта "Ключ към ключ" е много лек генератор и преобразувател на типове на тези стойности.

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::maps.

#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