C++ 双向映射是否有更有效的实现?
我创建了一个简单的双向映射类,通过内部存储两个C++ 双向映射是否有更有效的实现?,c++,c++11,data-structures,map,bimap,C++,C++11,Data Structures,Map,Bimap,我创建了一个简单的双向映射类,通过内部存储两个std::map实例,使用相反的键/值类型,并提供一个用户友好的界面: template<class T1, class T2> class Bimap { std::map<T1, T2> map1; std::map<T2, T1> map2; // ... }; 模板类Bimap { 地图地图1; 地图地图2; // ... }; 有没有更有效的方法实现不需要两倍内存的双向映射
std::map
实例,使用相反的键/值类型,并提供一个用户友好的界面:
template<class T1, class T2> class Bimap
{
std::map<T1, T2> map1;
std::map<T2, T1> map2;
// ...
};
模板类Bimap
{
地图地图1;
地图地图2;
// ...
};
- 有没有更有效的方法实现不需要两倍内存的双向映射
- bimap通常是如何实现的
编辑:
- bimap元素应该是可变的还是不可变的?(更改
中的一个元素应更改map1
中的键,但键是常量,这是不可能的-解决方案是什么?)map2
- 元素的所有权也是另一个问题:当用户在bimap中插入键值对时,bimap应该复制该键值对并将其存储,然后内部第二个映射(具有反转的键值)不应该复制,而应该指向原始对。如何做到这一点
编辑2:
将所有元素存储在一个向量中,并有两个
和
的映射,这样您就不会将所有元素复制两次
在我看来,你试图存储2种东西,元素本身以及它们之间的关系,如果你是针对标量类型,你可以将其保留为2个映射,但如果你是针对复杂类型,则将存储与关系分离更有意义,并处理存储外部的关系。利用
从链接的维基百科页面:
Boost-mutant-idiom使用了reinterpret_-cast,并且在很大程度上依赖于这样一种假设,即具有相同数据成员(类型和顺序)的两个不同结构的内存布局是可互换的。虽然C++标准不保证这个属性,实际上所有编译器都满足它。
模板
结构反转
{
typedef typename对::第一种类型第二种类型;
typedef typename对::第二种类型第一种类型;
第二类;
第一种类型优先;
};
模板
反向和变异(配对和p)
{
返回重新解释(p);
}
内部主(空)
{
标准:对p(1.34,5);
std::cout如果你为你的类型创建了一组对std::set
你基本上实现了你的函数性和关于易变性和常量预设的规则(好的,也许设置不是你想要的,但是可以进行调整)。下面是代码:
#ifndef MYBIMAP_HPP
#define MYBIMAP_HPP
#include <set>
#include <utility>
#include <algorithm>
using std::make_pair;
template<typename X, typename Y, typename Xless = std::less<X>,
typename Yless = std::less<Y>>
class bimap
{
typedef std::pair<X, Y> key_type;
typedef std::pair<X, Y> value_type;
typedef typename std::set<key_type>::iterator iterator;
typedef typename std::set<key_type>::const_iterator const_iterator;
struct Xcomp
{
bool operator()(X const &x1, X const &x2)
{
return !Xless()(x1, x2) && !Xless()(x2, x1);
}
};
struct Ycomp
{
bool operator()(Y const &y1, Y const &y2)
{
return !Yless()(y1, y2) && !Yless()(y2, y1);
}
};
struct Fless
{ // prevents lexicographical comparison for std::pair, so that
// every .first value is unique as if it was in its own map
bool operator()(key_type const &lhs, key_type const &rhs)
{
return Xless()(lhs.first, rhs.first);
}
};
/// key and value type are interchangeable
std::set<std::pair<X, Y>, Fless> _data;
public:
std::pair<iterator, bool> insert(X const &x, Y const &y)
{
auto it = find_right(y);
if (it == end()) { // every .second value is unique
return _data.insert(make_pair(x, y));
}
return make_pair(it, false);
}
iterator find_left(X const &val)
{
return _data.find(make_pair(val,Y()));
}
iterator find_right(Y const &val)
{
return std::find_if(_data.begin(), _data.end(),
[&val](key_type const &kt)
{
return Ycomp()(kt.second, val);
});
}
iterator end() { return _data.end(); }
iterator begin() { return _data.begin(); }
};
#endif
#如果不采用MYBIMAP水电站
#定义MYBIMAP_水电站
#包括
#包括
#include在所有简单的bimap实现中双重存储数据都存在一定的问题。如果您可以从外部将其分解为指针的bimap,那么您可以很容易地忽略这一点,只需像Arkaitz Jimenez建议的那样保留两个std::map
形式的映射(虽然与他的回答相反,你必须关心外部存储,以避免a->a*
查找)。但是如果你有指针,为什么不干脆将std::pair
存储在你本来可以分别存储a
和B
的位置呢
最好使用std::map
而不是std::map
,因为这样可以通过新创建的具有相同内容的字符串(而不是指向创建该对的原始字符串的指针)查找与字符串相关联的元素。但通常会在每个条目中存储密钥的完整副本并且只依赖散列来找到正确的bucket。这样即使在散列冲突的情况下,返回的项也将是正确的
如果你想让它又快又脏,有这个
黑客解决方案:
创建两个映射std::map mapA
和std::map mapB
。插入后,对要插入的两个元素进行散列,以获取相应映射的键
void insert(const A &a, const B &b) {
size_t hashA = std::hash<A>(a);
size_t hashB = std::hash<B>(b);
mapA.insert({hashB, a});
mapB.insert({hashA, b});
}
现在只需通过简单的std::set
查找和指针解引用即可完成查找
这个更好的解决方案与boost使用的解决方案类似——尽管它们使用一些注释化的指针作为对的第二个元素,因此必须使用reinterpret\u cast
s
请注意,.second
对的一部分需要是可变的(因此我不确定是否可以使用std::pair
),或者您必须添加另一层抽象(std::set mapA
)即使是这种简单的插入。在这两种解决方案中,您都需要临时元素来返回对元素的非常量引用。使用中间数据结构和间接寻址的一种可能实现是:
int sz; // total elements in the bimap
std::map<A, int> mapA;
std::map<B, int> mapB;
typedef typename std::map<A, int>::iterator iterA;
typedef typename std::map<B, int>::iterator iterB;
std::vector<pair<iterA, iterB>> register;
// All the operations on bimap are indirected through it.
int sz;//bimap中的元素总数
标准::地图这个怎么样
在这里,我们避免了一种类型(T1)的双重存储,而另一种类型(T2)仍然是双重存储
// Assume T1 is relatively heavier (e.g. string) than T2 (e.g. int family).
// If not, client should instantiate this the other way.
template <typename T1, typename T2>
class UnorderedBimap {
typedef std::unordered_map<T1, T2> Map1;
Map1 map1_;
std::unordered_map<T2, Map1::iterator> map2_;
};
//假设T1比T2(例如int系列)重(例如字符串)。
//如果不是,客户端应该以另一种方式实例化它。
模板
类无序BIMAP{
typedef std::无序的_映射1;
map1map1;
std::无序地图map2;
};
指向向量元素的指针会使插入/删除变得相当粗糙。我可能会使用列表作为备份存储,并使用迭代器作为两个映射。甚至,一个元素映射和一个指针。当树重新平衡时,节点本身不会移动。有趣的是。但是我应该使用std::vector
,因为指针可以不会失效,有些类可能是不可复制的。我想访问成员的效率会更低(但内存使用率会减半).Casey的观点是正确的,如果您希望频繁地插入/删除,那么最好使用一个支持存储,比如列表而不是向量,如果它是不可变的,向量将是一个不错的选择。为什么不让两个映射都包含std::shared_ptr?I
std::set<pair<B, A*>> mapA;
std::set<pair<A, B*>> mapB;
void insert(const A &a, const B &b) {
auto aitr = mapA.insert({b, nullptr}).first; // creates first pair
B *bp = &(aitr->first); // get pointer of our stored copy of b
auto bitr = mapB.insert({a, bp}).first;
// insert second pair {a, pointer_to_b}
A *ap = &(bitr->first); // update pointer in mapA to point to a
aitr->second = ap;
}
int sz; // total elements in the bimap
std::map<A, int> mapA;
std::map<B, int> mapB;
typedef typename std::map<A, int>::iterator iterA;
typedef typename std::map<B, int>::iterator iterB;
std::vector<pair<iterA, iterB>> register;
// All the operations on bimap are indirected through it.
// Assume T1 is relatively heavier (e.g. string) than T2 (e.g. int family).
// If not, client should instantiate this the other way.
template <typename T1, typename T2>
class UnorderedBimap {
typedef std::unordered_map<T1, T2> Map1;
Map1 map1_;
std::unordered_map<T2, Map1::iterator> map2_;
};