web-dev-qa-db-ja.com

Javaには逆ルックアップのHashMapがありますか?

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

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

95
Kip

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

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

105
uckelman

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

73
ColinD

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

    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);
        }
    }
19
GETah

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

11
rsp

ここに私の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つが置き換えられます。

3
Fulvius

GETah's answer に触発され、私は自分で似たようなものを書くことに決めました。

  • クラスは_Map<K,V>_- Interfaceを実装しています
  • 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;
        }
    }

}
_
0
Qw3ry

ここでかなり古い質問ですが、私がちょうどやったように他の誰かが脳をブロックしていて、これにつまずくなら、うまくいけばこれが助けになるでしょう。

私も双方向の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の方が簡単です。

0
ThatOneGuy