【问题标题】:Associative Key to Key map in C++C ++中的关联键到键映射
【发布时间】:2015-04-17 13:45:38
【问题描述】:

我正在寻找类似 C++ 中“Key to Key”映射的东西。

我的意图如下:

  • 每个键 - “左侧”或“右侧”都是唯一的
  • 左侧的键可以通过右侧的键查找,反之亦然

作为一个例子,为了让我的意图更清楚,在代码中,它可能看起来像:

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++ 内部表示为类型安全枚举类的字段。 使用“Key to Key”映射是这些值的非常轻量级的生成器和类型转换器。

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

【问题讨论】:

  • 你可以简单地使用两张地图。如果对象很大,请考虑将它们放在其他地方(例如,在向量中)并且只在地图中存储指针。或者使用共享指针。
  • 但这并不妨碍一张地图可能包含两个等价的值。
  • 在插入其中任何一个之前,您需要检查两个映射是否已包含键/值。
  • 看看 boost.bimap。

标签: c++ c++11 boost stl containers


【解决方案1】:

当我发布我的第一个答案时,我不知道存在像 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

【讨论】:

  • 关于检测重复项,std::map 的语义完全是零差异。只需调用insert/emplace 并检查返回值
  • @sehe 谢谢,我认为这应该很容易,但无法找出昨天的匆忙。根据您的建议更新了答案。
【解决方案2】:

您可以使用两个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

【讨论】:

  • 首先我不相信这个解决方案,因为我不喜欢为琐碎的问题自己煮汤,但也许这确实是实现它的最简单方法。这个解决方案也可以很容易地通过初始化列表进行扩展。让我在上面睡一晚。
  • @rralf 如果缺少初始化列表构造函数是您的主要反对意见,请查看我的其他答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 2011-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多