C++ 中的关联键到键映射
Associative Key to Key map in C++
我正在搜索 C++ 中的 "Key to Key" 映射之类的东西。
我的意图如下:
- 每个键 - "left" 或 "right" 端都是唯一的
- 左边的键可以通过右边的键查找,反之亦然
举个例子,为了让我的意图更清楚,在代码中,它可能看起来像:
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 容器或 boost。
为什么我觉得它有用?
假设您正在读取配置文件,并且还想写入或更改此配置文件。
此配置文件可能包含一些在 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);
您可以使用两个 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
当我发布我的第一个答案时,我并不知道 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。
我正在搜索 C++ 中的 "Key to Key" 映射之类的东西。
我的意图如下:
- 每个键 - "left" 或 "right" 端都是唯一的
- 左边的键可以通过右边的键查找,反之亦然
举个例子,为了让我的意图更清楚,在代码中,它可能看起来像:
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 容器或 boost。
为什么我觉得它有用?
假设您正在读取配置文件,并且还想写入或更改此配置文件。 此配置文件可能包含一些在 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);
您可以使用两个 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
当我发布我的第一个答案时,我并不知道 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。