web-dev-qa-db-ja.com

複数のキーでマップを実装する方法は?

Mapのように動作するデータ構造が必要ですが、その値にアクセスするには複数の(異なるタイプの)キーを使用します。
(あまり一般的ではない、たとえばtwoキー)

キーは一意であることが保証されています。

何かのようなもの:

MyMap<K1,K2,V> ...

次のような方法で:

getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...

提案はありますか?

私が考えることができる唯一のものは:
2つのマップを内部で使用するクラスを作成します。

EDIT一部の人々は、JavaのMapのキーとしてTuplepair、または同様のものを使用することを勧めていますが、これは動作しません私にとって:
上記のように、指定された2つのキーのうちの1つだけで値を検索できる必要があります。
マップはキーのハッシュコードを使用し、それらの同等性をチェックします。

139

2つのマップ。 1つのMap<K1, V>と1つのMap<K2, V>。単一のインターフェイスが必要な場合は、上記のメソッドを実装するラッパークラスを作成します。

90
Jeremy Huiskamp

Commons-collectionsはあなたが探しているものを提供します: https://commons.Apache.org/proper/commons-collections/apidocs/

これで、commons-collectionsが入力されたように見えます。

型付きバージョンは次の場所にあります: https://github.com/megamattron/collections-generic

これにより、ユースケースが正確にサポートされます。

 MultiKeyMap<k1,k2,...,kn,v> multiMap = ??
48
Nathan Feger

私はまだ2つのマップソリューションを提案しようとしていますが、

Map<K2, K1> m2;
Map<K1, V>  m1;

このスキームでは、任意の数のキー「エイリアス」を使用できます。

また、マップが同期しなくなることなく、任意のキーを使用して値を更新できます。

39
Logan Capaldo

さらに別の解決策は、 Googleのグアバ を使用することです

import com.google.common.collect.Table;
import com.google.common.collect.HashBasedTable;

Table<String, String, Integer> table = HashBasedTable.create();

使い方は本当に簡単です:

String row = "a";
String column = "b";
int value = 1;

if (!table.contains(row, column)) {
    table.put(row, column, value);
}

System.out.println("value = " + table.get(row, column));

メソッドHashBasedTable.create()は基本的に次のようなことをしています:

Table<String, String, Integer> table = Tables.newCustomTable(
        Maps.<String, Map<String, Integer>>newHashMap(),
        new Supplier<Map<String, Integer>>() {
    public Map<String, Integer> get() {
        return Maps.newHashMap();
    }
});

カスタムマップを作成しようとしている場合は、2番目のオプションを選択する必要があります(@Karatheodoryが示唆しているように)。

32
Tombart

次の「キー」クラスを宣言するとどうなりますか。

public class Key {
   public Object key1, key2, ..., keyN;

   public Key(Object key1, Object key2, ..., Object keyN) {
      this.key1 = key1;
      this.key2 = key2;
      ...
      this.keyN = keyN;
   }

   @Override   
   public boolean equals(Object obj) {
      if (!(obj instanceof Key))
        return false;
      Key ref = (Key) obj;
      return this.key1.equals(ref.key1) && 
          this.key2.equals(ref.key2) &&
          ...
          this.keyN.equals(ref.keyN)
   }

    @Override
    public int hashCode() {
        return key1.hashCode() ^ key2.hashCode() ^ 
            ... ^ keyN.hashCode();
    }

}

マップの宣言

Map<Key, Double> map = new HashMap<Key,Double>();

キーオブジェクトの宣言

Key key = new Key(key1, key2, ..., keyN)

地図を埋める

map.put(key, new Double(0))

マップからオブジェクトを取得する

Double result = map.get(key);
15
Guigon

提案、一部の回答者が提案したとおり:

public interface IDualMap<K1, K2, V> {

    /**
    * @return Unmodifiable version of underlying map1
    */
    Map<K1, V> getMap1();

    /**
    * @return Unmodifiable version of underlying map2
    */
    Map<K2, V> getMap2();

    void put(K1 key1, K2 key2, V value);

}

public final class DualMap<K1, K2, V>
        implements IDualMap<K1, K2, V> {

    private final Map<K1, V> map1 = new HashMap<K1, V>();

    private final Map<K2, V> map2 = new HashMap<K2, V>();

    @Override
    public Map<K1, V> getMap1() {
        return Collections.unmodifiableMap(map1);
    }

    @Override
    public Map<K2, V> getMap2() {
        return Collections.unmodifiableMap(map2);
    }

    @Override
    public void put(K1 key1, K2 key2, V value) {
        map1.put(key1, value);
        map2.put(key2, value);
    }
}

キーが特定のタイプでなければならないという要件を単に落とさない、つまり、単にMap <Object、V>を使用しないのはなぜですか。

ジェネリックは、余分な作業の価値がない場合があります。

4
Mike Kucera

同様の問題を解決するためにこれを作成しました。

データ構造

import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.Iterator;

public class HashBucket {
    HashMap<Object, ArrayList<Object>> hmap;

    public HashBucket() {
        hmap = new HashMap<Object, ArrayList<Object>>();
    }

    public void add(Object key, Object value) {
        if (hmap.containsKey(key)) {
            ArrayList al = hmap.get(key);
            al.add(value);
        } else {
            ArrayList al = new ArrayList<Object>();
            al.add(value);
            hmap.put(key, al);
        }
    }

    public Iterator getIterator(Object key) {
        ArrayList al = hmap.get(key);
        return hmap.get(key).iterator();

    }

}

値を取得する:

(注*オブジェクトを挿入された型にキャストします。私の場合は、イベントオブジェクトでした)

    public Iterator getIterator(Object key) {
        ArrayList al = hmap.get(key);
        if (al != null) {
            return hmap.get(key).iterator();
        } else {
            List<Object> empty = Collections.emptyList();
            return empty.iterator();
        }

    }

挿入

Event e1 = new Event();
e1.setName("Bob");
e1.setTitle("Test");
map.add("key",e1);
3
Jon Polaski

私は次のアプローチを見ることができます:

a)2つの異なるマップを使用します。あなたが提案するようにクラスでそれらをラップすることができますが、それでもやり過ぎかもしれません。マップを直接使用するだけです:key1Map.getValue(k1)、key2Map.getValue(k2)

b)タイプ認識キークラスを作成し、それを使用できます(未テスト)。

public class Key {
  public static enum KeyType { KEY_1, KEY_2 }

  public final Object k;
  public final KeyType t;

  public Key(Object k, KeyType t) {
    this.k = k;
    this.t= t;
  }

  public boolean equals(Object obj) {
    KeyType kt = (KeyType)obj;
    return k.equals(kt.k) && t == kt.t;
  }

  public int hashCode() {
   return k.hashCode() ^ t.hashCode();
  }
}

ところで、多くの一般的な場合、key1のスペースとkey2のスペースは交差しません。その場合、実際には特別なことをする必要はありません。エントリkey1=>vおよびkey2=>vを持つマップを定義するだけです

2
ykaganovich

sol:両方のキーを連結して最終キーを作成し、これをキーとして使用します。

キー値の場合、

ket-1とkey-2を間に「」で連結し、これを元のキーとして使用します。

key = key-1 + "、" + key-2;

myMap.put(key、value);

同様に値を取得します。

2
amjed

すべてのマルチキーはおそらく失敗し、put([key1、key2]、val)とget([null、key2])が[key1、key2]と[null、key2]の等値を使用することになります。バッキングマップにキーごとのハッシュバケットが含まれていない場合、ルックアップは非常に遅くなります。

私は行く方法はインデックスデコレータを使用することだと思います(上記のkey1、key2の例を参照)、余分なインデックスキーが格納された値のプロパティである場合、プロパティ名とリフレクションを使用してput(key 、val)を追加し、そのインデックスを使用するためにget(propertyname、propertyvalue)メソッドを追加します。

get(propertyname、propertyvalue)の戻り値の型はCollectionである可能性があるため、一意のキーがインデックス付けされていない場合でも...

2
bram

Pythonタプルのように聞こえます。その精神に従って、Comparableを実装する独自の工夫の不変クラスを作成できます。

1
duffymo

このような実装を複数のキーオブジェクトに使用しました。これにより、マップに無数のキーを使用できます。スケーラブルで非常にシンプルです。ただし、制限があります。キーはコンストラクターの引数の順序に従って並べられ、Arrays.equals()を使用するため、2D配列では機能しません。修正するには-Arrays.deepEquals()を使用できます。

それがあなたを助けることを願っています。そのような問題の解決策として使用できない理由を知っている場合は、お知らせください!

public class Test {

    private static Map<InnumerableKey, Object> sampleMap = new HashMap<InnumerableKey, Object>();

    private static class InnumerableKey {

        private final Object[] keyParts;

        private InnumerableKey(Object... keyParts) {
            this.keyParts = keyParts;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof InnumerableKey)) return false;

            InnumerableKey key = (InnumerableKey) o;

            if (!Arrays.equals(keyParts, key.keyParts)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return keyParts != null ? Arrays.hashCode(keyParts) : 0;
        }
    }

    public static void main(String... args) {
        boolean keyBoolean = true;
        double keyDouble = 1d;
        Object keyObject = new Object();

        InnumerableKey doubleKey = new InnumerableKey(keyBoolean, keyDouble);
        InnumerableKey tripleKey = new InnumerableKey(keyBoolean, keyDouble, keyObject);

        sampleMap.put(doubleKey, "DOUBLE KEY");
        sampleMap.put(tripleKey, "TRIPLE KEY");

        // prints "DOUBLE KEY"
        System.out.println(sampleMap.get(new InnumerableKey(true, 1d)));
        // prints "TRIPLE KEY"
        System.out.println(sampleMap.get(new InnumerableKey(true, 1d, keyObject)));
        // prints null
        System.out.println(sampleMap.get(new InnumerableKey(keyObject, 1d, true)));
    }
}
1
Gadget

たとえば、ソートのためだけにマップを使用する場合の汚い単純な解決策は、値が存在しなくなるまで非常に小さな値をキーに追加することですが、最小値(たとえばDouble.MIN_VALUE)を追加しないことです。バグが発生します。私が言ったように、これは非常に汚い解決策ですが、コードを簡単にします。

0
Damir Olejar

あなたの質問にあなたが望む方法は、Mapによって直接サポートされているように思えます。あなたが望むと思われるものは

put(K1 key, K2 key, V value)
put(K1 key, V value)
put(K2 key, V value)

マップでは、get()containsKey()などはすべてObject引数を取ることに注意してください。 1つのget()メソッドを使用して、結合するすべての複合マップに委任することを妨げるものは何もありません(質問およびその他の回答に記載されているとおり)。おそらく、クラス登録の問題が発生しないように型登録が必要になるでしょう(特別な場合+単純に実装されている場合)。

型付き登録により、使用する「正しい」マップを取得することもできます。

Map<T,V> getMapForKey(Class<T> keyClass){
  //Completely naive implementation - you actually need to 
  //iterate through the keys of the maps, and see if the keyClass argument
  //is a sub-class of the defined map type.  And then ordering matters with 
  //classes that implement multiple interfaces...
  Map<T,V> specificTypeMap = (Map<T,V) maps.get(keyClass);
  if (specificTypeMap == null){
     throw new IllegalArgumentException("There is no map keyed by class" + keyClass);
  }
  return maps.get(keyClass);
}

V put(Object key, V value) {
  //This bit requires generic suppression magic - but 
  //nothing leaves this class and you're testing it right? 
  //(You can assert that it *is* type-safe)
  Map map = getMapForKey(key.getClass());
  map.put(object, key);
}

void put(Object[] keys, V value) { //Or put(V value, Object ... keys)
   //Might want to catch exceptions for unsupported keys and log instead?
   .....
}

いくつかのアイデア...

0
Stephen

構造を提案します

Map<K1, Map<K2, V>>

2番目のキーの検索は効率的ではないかもしれませんが

0
kon psych

K1およびK2のインスタンスを持つクラスを定義します。次に、それをクラスとしてキータイプとして使用します。

0
marcog

Googleコレクション をご覧ください。または、あなたが提案するように、マップを内部で使用し、そのマップにペアを使用させます。 Pair <>を書くか見つける必要があります。とても簡単ですが、標準のコレクションの一部ではありません。

0
Carl Manaster

あなたのソリューションはこのニーズに非常に妥当であるように思えますが、あなたの2つのキータイプが本当に異なる場合、正直に言って問題はありません。このための独自の実装を作成し、必要に応じて同期の問題に対処するだけです。

0
Uri

私はこのようなものをお勧めします:

    public class MyMap {

      Map<Object, V> map = new HashMap<Object, V>();


      public V put(K1 key,V value){
        return map.put(key, value);
      }

      public V put(K2 key,V value){
        return map.put(key, value);
      }

      public V get(K1 key){    
        return map.get(key);
      }

      public V get(K2 key){    
        return map.get(key);
      }

      //Same for conatains

    }

その後、次のように使用できます。
myMap.put(k1,value)またはmyMap.put(k2,value)

利点:シンプルで、タイプセーフを適用し、繰り返しデータを保存しません(2つのマップソリューションが保存するように、重複した値を保存します)。
欠点:一般的ではありません。

0
user454322

複数のキーの組み合わせを1つとして使用する場合は、おそらくApache commons MultiKey があなたの友人です。しかし、私はそれが一つ一つうまくいくとは思わない。

0
Dima

使用方法に応じて、2つのマップMap<K1, V>およびMap<K2, V>を使用するか、2つのマップMap<K1, V>およびMap<K2, K1>を使用してこれを実行できます。キーの1つが他のキーより永続的である場合、2番目のオプションの方が意味があります。

0
Kevin Peterson

このようなものはどうですか:

彼の声明では、キーは一意であるため、異なるキーに対して同じ値オブジェクトを保存することは非常に可能であり、その値に一致するキーを送信すると、値オブジェクトに戻ることができます。

以下のコードを参照してください:

値オブジェクトクラス、

    public class Bond {
    public Bond() {
        System.out.println("The Name is Bond... James Bond...");
    }
    private String name;
    public String getName() { return name;}
    public void setName(String name) { this.name = name; }
}

public class HashMapValueTest {

    public static void main(String[] args) {

        String key1 = "A";
        String key2 = "B";
        String key3 = "C";

        Bond bond = new Bond();
        bond.setName("James Bond Mutual Fund");

        Map<String, Bond> bondsById = new HashMap<>();

        bondsById.put(key1, bond);
        bondsById.put(key2, bond);
        bondsById.put(key3, bond);

        bond.setName("Alfred Hitchcock");

        for (Map.Entry<String, Bond> entry : bondsById.entrySet()) {
            System.out.println(entry.getValue().getName());
        }

    }

}

結果は次のとおりです。

The Name is Bond... James Bond...

Alfred HitchCock

Alfred HitchCock

Alfred HitchCock
0
Veeren Jote

より複雑なキーの可能性を提供する別の可能なソリューションは、ここにあります: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html

0
Jan Wiemer

CommonsまたはGuavaのMultiMapまたはMultiKeyMapの両方が機能します。

ただし、キーがプリミティブ型であることを考慮して、コンポジットキーを処理するMapクラスの購入を自分で拡張することは、迅速かつ簡単な解決策です。

0
AlexVPerl

キーが一意である場合、2つのマップ、マップのマップ、mapOfWhateverThereIsは必要ありません。単一のマップと、そのマップにキーと値を配置する単純なラッパーメソッドのみが必要です。例:

Map<String, String> map = new HashMap<>();

public void addKeysAndValue(String key1, String key2, String value){
    map.put(key1, value);
    map.put(key2, value);
}

public void testIt(){
    addKeysAndValue("behemoth", "hipopotam", "hornless rhino");
}

次に、通常どおりマップを使用します。これらの派手なgetByKeyNやcontainsKeyNも必要ありません。

0
VasiliyL

トライデータ構造を使用してはどうですか?

http://en.wikipedia.org/wiki/Trie

トライのルートは空白になります。最初のレベルの兄弟はマップのプライマリキー、2番目のレベルの兄弟はセカンダリキー、3番目のレベルは値を持つターミナルノードであり、nullはそのブランチの終了を示します。同じスキームを使用して3つ以上のキーを追加することもできます。

ルックアップは単純なDFSです。

0
user2856450