単純な双方向mapクラスを作成しました。これは、2つのstd::map
インスタンスを内部的に格納し、反対のキー/値タイプで動作し、ユーザーフレンドリーなインターフェースを提供します:
template<class T1, class T2> class Bimap
{
std::map<T1, T2> map1;
std::map<T2, T1> map2;
// ...
};
2倍のメモリを必要としない双方向マップを実装するより効率的な方法はありますか?
通常、ビマップはどのように実装されますか?
編集:
Bimap要素は可変または不変である必要がありますか? (map1
の1つの要素を変更すると、map2
のキーを変更する必要がありますが、キーはconstであり、不可能です-解決策は何ですか?)
要素の所有権も別の問題です。ユーザーがキーと値のペアをbimapに挿入すると、bimapはそのキーと値のペアのコピーを作成して保存し、次に内部の2番目のマップ(反転キー/値付き)コピーではなく、元のペアを指します。どうすればこれを達成できますか?
編集2:
Bimapのすべての単純な実装でのデータの二重保存には、特定の問題があります。外部からポインターのバイマップに分解できる場合は、これを簡単に無視して、Arkaitz Jimenezが既に提案したstd::map<A*,B*>
の形式の両方のマップを保持することができます(ただし、彼の答えに反して、 A->A*
ルックアップを回避するための外部からのストレージ)。しかし、とにかくポインタを持っている場合、A
とB
を別々に保存するポイントにstd::pair<A,B>
を単に保存しないのはなぜですか?
std::map<A,B*>
の代わりにstd::map<A*,B*>
を使用すると、たとえば、元の文字列へのポインタの代わりに、同じコンテンツで新しく作成された文字列によって文字列に関連付けられた要素を検索できます。ペアを作成しました。ただし、すべてのエントリでキーの完全なコピーを保存し、適切なバケットを見つけるためにハッシュのみに依存するのが一般的です。このように、返されるアイテムは、ハッシュ衝突の場合でも正しいものになります...
あなたがそれを素早く汚したいのであれば、これがあります
ハック的なソリューション:
2つのマップ
std::map<size_t, A> mapA
およびstd::map<size_t, B> 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}); }
ルックアップも同様に実装されます。
multimap
の代わりにmap
を使用し、それぞれ他のマップのルックアップで取得したすべての要素を検証します(b
から候補mapA
を取得、ハッシュb
で、必要なキーと一致する場合はmapB
を検索し、そうでない場合は次の候補bを反復処理します)これは有効な実装ですが、私の意見ではまだハックです...
エントリ(上記を参照)をストレージとしてのみ比較するために使用される要素のコピーを使用することにより、より優れたソリューションを得ることができます。しかし、それを回避するのは少し難しいです。詳しく説明するには:
より良い解決策:
std::set<pair<A, B*>>
およびstd::set<pair<B, A*>>
として2組のペアを作成し、operator<
およびoperator==
をオーバーロードして、ペアの最初の要素のみを考慮する(または対応する比較クラスを提供する) 。A
とB
が常にメモリ内の同じ位置にあることを保証する必要があるため、マップ(内部的には同じように見える)の代わりにペアのセットを作成する必要があります。pair<A,B>
を挿入すると、上記のセットに収まる2つの要素に分割されます。
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;
}
ルックアップは、単純な
std::set
ルックアップとポインターの間接参照によって簡単に実行できるようになりました。
この優れたソリューションは、ブーストが使用するソリューションに似ています-たとえペアの2番目の要素として匿名化されたポインターを使用しているため、reinterpret_cast
sを使用する必要があります。
ペアの.second
部分は可変である必要があるため(std::pair
を使用できるかどうかはわかりません)、または抽象化の別のレイヤー(std::set<pair<B, A**>> mapA
)を追加する必要がありますこの簡単な挿入のため。どちらのソリューションでも、要素への非const参照を返すために一時的な要素が必要です。
すべての要素をベクトルに格納し、<T1*,T2*>
と<T2*,T1*>
の2つのマップを用意して、すべてを2回コピーしないようにする方が効率的です。
私が見る方法では、2つのもの、要素自体とそれらの間の関係を保存しようとしています。スカラー型を目指している場合は2つのマップをそのままにしておくことができますが、複雑な型を扱うことを目指す場合はより理にかなっていますストレージを関係から分離し、ストレージ外の関係を処理します。
Boost Bimap は Boost Mutant Idiom を使用します。
リンクされたウィキペディアのページから:
Boostミュータントイディオムはreinterpret_castを使用し、同一のデータメンバー(タイプと順序)を持つ2つの異なる構造のメモリレイアウトが交換可能であるという仮定に大きく依存しています。 C++標準ではこのプロパティが保証されていませんが、実質的にすべてのコンパイラーがこのプロパティを満たします。
template <class Pair>
struct Reverse
{
typedef typename Pair::first_type second_type;
typedef typename Pair::second_type first_type;
second_type second;
first_type first;
};
template <class Pair>
Reverse<Pair> & mutate(Pair & p)
{
return reinterpret_cast<Reverse<Pair> &>(p);
}
int main(void)
{
std::pair<double, int> p(1.34, 5);
std::cout << "p.first = " << p.first << ", p.second = " << p.second << std::endl;
std::cout << "mutate(p).first = " << mutate(p).first << ", mutate(p).second = " << mutate(p).second << std::endl;
}
ブーストソースでの実装は、もちろんかなり複雑です。
タイプstd::set<std::pair<X,Y>>
にペアのセットを作成すると、機能性がほとんど実装され、多安定性と安定性のプリセットに関するルール(OKおそらく設定はあなたの望むものではないかもしれませんが、微調整はできます作られます)。コードは次のとおりです。
#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
使用例
template<typename X, typename Y, typename In>
void PrintBimapInsertion(X const &x, Y const &y, In const &in)
{
if (in.second) {
std::cout << "Inserted element ("
<< in.first->first << ", " << in.first->second << ")\n";
}
else {
std::cout << "Could not insert (" << x << ", " << y
<< ") because (" << in.first->first << ", "
<< in.first->second << ") already exists\n";
}
}
int _tmain(int argc, _TCHAR* argv[])
{
bimap<std::string, int> mb;
PrintBimapInsertion("A", 1, mb.insert("A", 1) );
PrintBimapInsertion("A", 2, mb.insert("A", 2) );
PrintBimapInsertion("b", 2, mb.insert("b", 2));
PrintBimapInsertion("z", 2, mb.insert("z", 2));
auto it1 = mb.find_left("A");
if (it1 != mb.end()) {
std::cout << std::endl << it1->first << ", "
<< it1->second << std::endl;
}
auto it2 = mb.find_right(2);
if (it2 != mb.end()) {
std::cout << std::endl << it2->first << ", "
<< it2->second << std::endl;
}
return 0;
}
[〜#〜] note [〜#〜]:これはすべて、完全な実装がどうなるかを示す大まかなコードであり、コードを洗練して拡張した後でも、これがそうなることを意味するわけではありません。 boost::bimap
の代わりになりますが、値とキーの両方で検索可能な連想コンテナを持つための単なるhomemade方法です。
中間データ構造とインダイレクションを使用する1つの可能な実装は次のとおりです。
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.
挿入
XとYがそれぞれAとBのインスタンスである(X、Y)を挿入する必要があるとします:
mapA
に(X、sz)を挿入--- O(lg sz)mapB
に(Y、sz)を挿入--- O(lg sz)register
のPush_back
(IterX、IterY)--- O(1)。ここで、IterXとIterYは、mapA
とmapB
の対応する要素の反復子であり、それぞれ(1)と(2)から取得されます。ルックアップ
タイプAの要素Xの画像の検索:
mapA
のXにマップされたintを取得します。 --- O(lg n)register
にインデックスを付け、対応するIterYを取得します。 --- O(1)IterY->first
でYを取得できます。 --- O(1)したがって、両方の操作はO(lg n)で実装されます。
Space:AとBのオブジェクトのすべてのコピーは、一度だけ保存する必要があります。ただし、簿記に関するものはたくさんあります。しかし、大きなオブジェクトがある場合、それもあまり重要ではありません。
注:この実装は、マップの反復子が決して無効化されないという事実に依存しています。したがって、register
の内容は常に有効です。
この実装のより精巧なバージョンを見つけることができます here
これはどう?
ここでは、1つのタイプ(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_;
};