簡體   English   中英

Java:哈希圖中的復合鍵

[英]Java: Composite key in hashmaps

我想將一組對象存儲在哈希圖中,其中鍵應該是兩個字符串值的組合。 有沒有辦法做到這一點?

我可以簡單地連接兩個字符串,但我確信有更好的方法來做到這一點。

您可以有一個包含兩個字符串的自定義對象:

class StringKey {
    private String str1;
    private String str2;
}

問題是,您需要確定兩個此類對象的相等性測試和哈希碼。

相等性可以是兩個字符串的匹配,哈希碼可以是串聯成員的哈希碼(這是有爭議的):

class StringKey {
    private String str1;
    private String str2;

    @Override
    public boolean equals(Object obj) {
        if(obj != null && obj instanceof StringKey) {
            StringKey s = (StringKey)obj;
            return str1.equals(s.str1) && str2.equals(s.str2);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (str1 + str2).hashCode();
    }
}

您無需重新發明輪子。 根據您的需要,只需使用Table<R,C,V>接口的GuavaHashBasedTable<R,C,V>實現。 這是一個例子

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

table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);

System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100

table.put("key-1", "lock-1", 150); //replaces 50 with 150

編碼愉快!

public int hashCode() {
    return (str1 + str2).hashCode();
}

這似乎是一種生成哈希代碼的糟糕方法:每次計算哈希代碼時都創建一個新的字符串實例是糟糕的。 (即使生成一次字符串實例並緩存結果也是不好的做法。)

這里有很多建議:

如何為字符串列表計算一個好的哈希碼?

public int hashCode() {
    final int prime = 31;
    int result = 1;
    for ( String s : strings ) {
        result = result * prime + s.hashCode();
    }
    return result;
}

對於一對字符串,它變為:

return string1.hashCode() * 31 + string2.hashCode();

這是一個非常基本的實現。 通過鏈接提供很多建議,以建議更好的調整策略。

為什么不創建一個(比如) Pair對象,其中包含兩個字符串作為成員,然后將其用作鍵?

例如

public class Pair {
   private final String str1;
   private final String str2;

   // this object should be immutable to reliably perform subsequent lookups
}

不要忘記equals()hashCode() 有關 HashMap 和鍵的更多信息,包括不變性要求的背景,請參閱此博客條目 如果您的密鑰不是不可變的,那么您可以更改其組件,隨后的查找將無法找到它(這就是為什么不可變對象(例如String是密鑰的良好候選者)

你是對的,串聯並不理想。 在某些情況下它會起作用,但它通常是一個不可靠且脆弱的解決方案(例如, AB/C是與A/BC不同的密鑰嗎?)。

我有一個類似的案例。 我所做的就是連接兩個由波浪號 (~) 分隔的字符串。

所以當客戶端調用服務函數從地圖中獲取對象時,它看起來是這樣的:

MyObject getMyObject(String key1, String key2) {
    String cacheKey = key1 + "~" + key2;
    return map.get(cachekey);
}

這很簡單,但很管用。

我看到很多人使用嵌套地圖。 也就是說,要映射Key1 -> Key2 -> Value (我使用計算機科學/ aka haskell curring notation for (Key1 x Key2) -> Value mapping 有兩個參數並產生一個值),你首先提供第一個鍵 - - 這會返回一個(部分)映射Key2 -> Value ,您將在下一步中展開它。

例如,

Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance

add(k1, k2, value) {
  table2 = table1.get(k1);
  if (table2 == null) table2 = table1.add(k1, new HashMap())
  table2.add(k2, value)
}

get(k1, k2) {
  table2 = table1.get(k1);
  return table2.get(k2)
}

我不確定它是否比普通復合鍵結構更好。 你可以對此發表評論。

閱讀有關 spaguetti/cactus 堆棧的信息,我想出了一個可能用於此目的的變體,包括以任何順序映射您的鍵的可能性,以便 map.lookup("a","b") 和 map.lookup(" b","a") 返回相同的元素。 它還適用於任意數量的鍵,而不僅僅是兩個。

我將它用作堆棧來試驗數據流編程,但這里有一個快速而骯臟的版本,它用作多鍵映射(應該改進:應該使用集合而不是數組來避免查找鍵的重復出現)

public class MultiKeyMap <K,E> {
    class Mapping {
        E element;
        int numKeys;
        public Mapping(E element,int numKeys){
            this.element = element;
            this.numKeys = numKeys;
        }
    }
    class KeySlot{
        Mapping parent;
        public KeySlot(Mapping mapping) {
            parent = mapping;
        }
    }
    class KeySlotList extends LinkedList<KeySlot>{}
    class MultiMap extends HashMap<K,KeySlotList>{}
    class MappingTrackMap extends HashMap<Mapping,Integer>{}

    MultiMap map = new MultiMap();

    public void put(E element, K ...keys){
        Mapping mapping = new Mapping(element,keys.length);
        for(int i=0;i<keys.length;i++){
            KeySlot k = new KeySlot(mapping);
            KeySlotList l = map.get(keys[i]);
            if(l==null){
                l = new KeySlotList();
                map.put(keys[i], l);
            }
            l.add(k);
        }
    }
    public E lookup(K ...keys){
        MappingTrackMap tmp  = new MappingTrackMap();
        for(K key:keys){
            KeySlotList l = map.get(key);
            if(l==null)return null;
            for(KeySlot keySlot:l){
                Mapping parent = keySlot.parent;
                Integer count = tmp.get(parent);
                if(parent.numKeys!=keys.length)continue;
                if(count == null){
                    count = parent.numKeys-1;
                }else{
                    count--;
                }
                if(count == 0){
                    return parent.element;
                }else{
                    tmp.put(parent, count);
                }               
            }
        }
        return null;
    }
    public static void main(String[] args) {
        MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
        m.put("brazil", "yellow", "green");
        m.put("canada", "red", "white");
        m.put("USA", "red" ,"white" ,"blue");
        m.put("argentina", "white","blue");

        System.out.println(m.lookup("red","white"));  // canada
        System.out.println(m.lookup("white","red"));  // canada
        System.out.println(m.lookup("white","red","blue")); // USA
    }
}
public static String fakeMapKey(final String... arrayKey) {
    String[] keys = arrayKey;

    if (keys == null || keys.length == 0)
        return null;

    if (keys.length == 1)
        return keys[0];

    String key = "";
    for (int i = 0; i < keys.length; i++)
        key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");

    keys = Arrays.copyOf(keys, keys.length + 1);

    keys[keys.length - 1] = FAKE_KEY_SEPARATOR;

    return  MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";

INPUT: fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3

我想提一下我認為其他答案中沒有涵蓋的兩個選項。 它們是否適合您的目的,您必須自己決定。

地圖<字符串,地圖<字符串,你的對象>>

您可以使用地圖的地圖,在外部地圖中使用字符串 1 作為鍵,在每個內部地圖中使用字符串 2 作為鍵。

我不認為它在語法方面是一個很好的解決方案,但它很簡單而且我已經看到它在某些地方使用過。 它在時間和內存方面也應該是高效的,但在 99% 的情況下這不應該是主要原因。 我不喜歡的是我們丟失了關於密鑰類型的明確信息:它只是從代碼中推斷出有效密鑰是兩個字符串,閱讀起來並不清晰。

地圖<你的對象,你的對象>

這是針對特殊情況的。 這種情況我遇到過不止一次,所以沒有比這更特別的了。 如果您的對象包含用作鍵的兩個字符串,並且基於這兩者定義對象相等性是有意義的,則相應地定義equalshashCode並將該對象用作鍵和值。

在這種情況下,人們可能希望使用Set而不是Map ,但是 Java HashSet不提供任何方法來從基於相等對象的集合中檢索對象。 所以我們確實需要地圖。

一個責任是您需要創建一個新對象才能進行查找。 這也適用於許多其他答案中的解決方案。

關聯

Jerónimo López:HashMaps 中關於 map of maps 效率的 Composite key

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM