Javaには逆引き参照付きのHashMapがありますか?


98

「Key-Value」ではなく「Key-Key」形式で整理されたデータがあります。これはHashMapに似ていますが、双方向でO(1)ルックアップが必要になります。このタイプのデータ構造に名前はありますか?このようなものはJavaの標準ライブラリに含まれていますか?(またはおそらくApache Commons?)

基本的に2つのミラー化されたマップを使用する独自のクラスを作成することもできますが、ホイールを再発明したくありません(これが既に存在するが、適切な用語を検索していない場合)。

回答:


106

Java APIにはそのようなクラスはありません。必要なApache Commonsクラスは、BidiMapの実装の1つになります。

数学者として、私はこの種の構造を全単射と呼びます。


82
非数学者として、私は「あなたがキーまたは他の方法で回避して値を検索することができますどのようなマップ」この種の構造呼ぶだろう
ドナル・

4
残念なことに、ジェネリックをサポートしていないようです。
エランメダン2012

2
github.com/megamattron/collections-genericは、GenericsをサポートするBidiMapを備えています
Kenston Choi 2013

1
@Don "Bidi"-> "Bi-Directional"
ryvantage

3
@Donalはい、しかしIT全体は数学に基づいています
Alex

75

Apache Commonsに加えて、GuavaにはBiMapもあります。


情報をありがとう!私はとりあえずapacheにこだわっています(そうしない理由があるのでない限り)
Kip

私はapacheコレクションとの良い比較を提供することはできませんが、googleコレクションには、検討する価値があると思う素晴らしいものがたくさんあります。
ColinD 2009年

16
Googleコレクションの利点の1つは、ジェネリックが含まれているのに対し、コモンズコレクションには含まれていないことです。
マーク

3
2つのライブラリの比較については、この回答の引用を参照してください:stackoverflow.com/questions/787446/…(および元のインタビュー)。明らかな理由から、それはGoogleに偏っていますが、それでも、今日のGoogleコレクションのほうが良いと言っても安全だと思います。
Jonik、2009年

1
BiMapリンクが壊れています。こちらをご利用ください。
Mahsa2

20

これを実行するために使用した簡単なクラスを次に示します(別のサードパーティの依存関係は必要ありませんでした)。マップで利用できるすべての機能を提供しているわけではありませんが、良いスタートです。

    public class BidirectionalMap<KeyType, ValueType>{
        private Map<KeyType, ValueType> keyToValueMap = new ConcurrentHashMap<KeyType, ValueType>();
        private Map<ValueType, KeyType> valueToKeyMap = new ConcurrentHashMap<ValueType, KeyType>();

        synchronized public void put(KeyType key, ValueType value){
            keyToValueMap.put(key, value);
            valueToKeyMap.put(value, key);
        }

        synchronized public ValueType removeByKey(KeyType key){
            ValueType removedValue = keyToValueMap.remove(key);
            valueToKeyMap.remove(removedValue);
            return removedValue;
        }

        synchronized public KeyType removeByValue(ValueType value){
            KeyType removedKey = valueToKeyMap.remove(value);
            keyToValueMap.remove(removedKey);
            return removedKey;
        }

        public boolean containsKey(KeyType key){
            return keyToValueMap.containsKey(key);
        }

        public boolean containsValue(ValueType value){
            return keyToValueMap.containsValue(value);
        }

        public KeyType getKey(ValueType value){
            return valueToKeyMap.get(value);
        }

        public ValueType get(KeyType key){
            return keyToValueMap.get(key);
        }
    }

5
containsValue()を変更してvalueToKeyMap.containsKey(value)
JT

キー(または値)が別の値(またはキー)で再度追加されると双方向性が失われるため、このマップは現状では使用しません。これは、キーIMOを更新するための有効な使用法です。
Qw3ry 2017

11

衝突が発生しない場合は、常に同じHashMapに両方向を追加できます:-)


6
@キップ:なぜ?一部のコンテキストでは、これは完全に正当なソリューションです。したがって、2つのハッシュマップが存在します。
Lawrence Dol、

7
いいえ、それは醜い、壊れやすいハックです。すべてのget()およびput()で双方向プロパティを維持する必要があり、双方向プロパティを知らなくてもマップを変更する他のメソッドに渡すことができます。おそらくどこにも渡されないメソッド内のローカル変数として、または作成直後に変更不可にされた場合は問題ないかもしれません。しかし、それでも脆弱です(誰かがやって来て、その機能を微調整し、双方向性を壊しますが、必ずしもすぐに問題になるとは限りません)
Kip

1
@Kip、そのような使用法はそのマップを使用するクラスの内部で維持する必要があることに同意しますが、最後のコメントは対応するJUnitテストがずさんな場合にのみ当てはまります:-)
rsp

ここでアセンブリ言語命令のオペコードをデコード/エンコードするためのマップが必要であることを想像して、そのような実装の非常に有効な使用法を提示する場合、マップの状態を変更することはありません。一方向では、キーは他のバイナリ値の命令文字列です。ですから、衝突は決してないはずです。
MK、

小規模な検索目的のために、そのハックは私の問題を解決します。
milkersarac 2016

5

ここに私の2セント。

または、ジェネリックで簡単なメソッドを使用できます。ケーキ。

public static <K,V> Map<V, K> invertMap(Map<K, V> toInvert) {
    Map<V, K> result = new HashMap<V, K>();
    for(K k: toInvert.keySet()){
        result.put(toInvert.get(k), k);
    }
    return result;
}

もちろん、一意の値を持つマップが必要です。それ以外の場合は、そのうちの1つが置き換えられます。


1

GETahの回答に触発されて、私はいくつかの改良を加えて自分で同様の何かを書くことにしました:

  • クラスは Map<K,V> -Interfaceをます
  • 双方向性は、値をaによって変更するときにそれを処理することによって本当に保証されますput(少なくともここで保証したいと思います)

使い方は通常のマップと同じで、マッピングの呼び出しを逆に見ることができます getReverseView()。コンテンツはコピーされず、ビューのみが返されます。

これが完全にフールプルーフであるかどうかは確かではありません(実際にはそうではない可能性があります)。

public class BidirectionalMap<Key, Value> implements Map<Key, Value> {

    private final Map<Key, Value> map;
    private final Map<Value, Key> revMap;

    public BidirectionalMap() {
        this(16, 0.75f);
    }

    public BidirectionalMap(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public BidirectionalMap(int initialCapacity, float loadFactor) {
        this.map = new HashMap<>(initialCapacity, loadFactor);
        this.revMap = new HashMap<>(initialCapacity, loadFactor);
    }

    private BidirectionalMap(Map<Key, Value> map, Map<Value, Key> reverseMap) {
        this.map = map;
        this.revMap = reverseMap;
    }

    @Override
    public void clear() {
        map.clear();
        revMap.clear();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return revMap.containsKey(value);
    }

    @Override
    public Set<java.util.Map.Entry<Key, Value>> entrySet() {
        return Collections.unmodifiableSet(map.entrySet());
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public Set<Key> keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    @Override
    public void putAll(Map<? extends Key, ? extends Value> m) {
        m.entrySet().forEach(e -> put(e.getKey(), e.getValue()));
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public Collection<Value> values() {
        return Collections.unmodifiableCollection(map.values());
    }

    @Override
    public Value get(Object key) {
        return map.get(key);
    }

    @Override
    public Value put(Key key, Value value) {
        Value v = remove(key);
        getReverseView().remove(value);
        map.put(key, value);
        revMap.put(value, key);
        return v;
    }

    public Map<Value, Key> getReverseView() {
        return new BidirectionalMap<>(revMap, map);
    }

    @Override
    public Value remove(Object key) {
        if (containsKey(key)) {
            Value v = map.remove(key);
            revMap.remove(v);
            return v;
        } else {
            return null;
        }
    }

}

BiMapおよびBidiMapと同様に、これは同じ値を持つ複数のキーを持つことを許可しない全単射であることに注意してください。(getReverseView()。get(v)は常に1つのキーのみを返します)。
ドナテッロ

本当ですが、OTOHはまさにOPが要求したものです
Qw3ry

彼のデータがこの制約に一致することを彼が表明したかどうかはわかりませんが、とにかく、それは他の誰かがよりよく理解するのに役立ちます!
ドナテッロ

0

ここではかなり古い質問ですが、他の誰かが私のように脳ブロックを起こしてこれにつまずいた場合、うまくいけばこれが役立つでしょう。

私も双方向のHashMapを探していましたが、最も便利な答えが最も単純な場合もあります。

車輪を作り直したくなく、他のライブラリやプロジェクトをプロジェクトに追加したくない場合は、並列配列(または、設計で必要な場合はArrayLists)の単純な実装はどうでしょうか。

SomeType[] keys1 = new SomeType[NUM_PAIRS];
OtherType[] keys2 = new OtherType[NUM_PAIRS];

2つのキーの1つのインデックスがわかったらすぐに、もう一方を簡単にリクエストできます。したがって、ルックアップメソッドは次のようになります。

SomeType getKey1(OtherType ot);
SomeType getKey1ByIndex(int key2Idx);
OtherType getKey2(SomeType st); 
OtherType getKey2ByIndex(int key2Idx);

これは、メソッドだけがこれらの配列/ ArrayListを変更する適切なオブジェクト指向構造を使用していることを前提としているため、それらを並列に保つことは非常に簡単です。タンデムで追加/削除する限り、配列のサイズが変化しても再構築する必要がないため、ArrayListの方がさらに簡単です。


3
HashMapの重要な機能、つまりO(1)ルックアップを失っています。このような実装では、探している項目のインデックス(O(n))が見つかるまで、配列の1つをスキャンする必要があります
Kip

ええ、それは真実であり、かなり大きな欠点の1つです。しかし、私の個人的な状況では、実際には3方向のキー一覧の必要性に対処しており、常に少なくとも1つのキーを事前に知っていたので、個人的にはそれは問題ではありませんでした。指摘していただきありがとうございます。元の投稿ではその重要な事実をスキップしているようです。
ThatOneGuy 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.