Java:散列表中的组合键

qco9c6ql  于 2023-02-07  发布在  Java
关注(0)|答案(9)|浏览(147)

我想在散列表中存储一组对象,其中键应该是两个字符串值的组合。有办法实现吗?
我可以简单地连接这两个字符串,但我相信有一个更好的方法来做到这一点。

tvmytwxo

tvmytwxo1#

您可以有一个包含以下两个字符串的自定义对象:

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

问题是,您需要确定两个这样的对象的相等性测试和散列代码。
Equality可以是两个字符串的匹配,hashcode可以是连接成员的hashcode(这是有争议的):

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();
    }
}
yzckvree

yzckvree2#

您不需要重新发明轮子,只需根据需要使用GuavaHashBasedTable<R,C,V>接口的Table<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
kd3sttzy

kd3sttzy3#

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

这似乎是一种生成hashCode的糟糕方法:每次计算哈希代码时都创建一个新的字符串示例是很糟糕的!(即使只生成一次字符串示例并缓存结果也是很糟糕的做法)。
这里有很多建议:
How do I calculate a good hash code for a list of strings?

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();

那是一个非常基本的实现。通过链接有很多建议来建议更好的调整策略。

58wvjzkj

58wvjzkj4#

为什么不创建一个(比方说)Pair对象,其中包含这两个字符串作为成员,然后使用它作为键呢?
例如:

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

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

不要忘了equals()hashCode()。请参阅this blog entry以获得更多关于HashMap和键的信息,包括不变性要求的背景知识。如果你的键不是不可变的,那么你可以改变它的组件,随后的查找将无法找到它(这就是为什么像String这样的不可变对象是键的好候选对象)
你说得对,这种连接并不理想,在某些情况下它可以工作,但它通常是一种不可靠和脆弱的解决方案(例如,AB/C是不是与A/BC不同的键?)

bkkx9g8r

bkkx9g8r5#

我有一个类似的例子,我所做的就是连接两个用波浪号(~)分隔的字符串。
所以当客户端调用service函数从map中获取对象时,它看起来像这样:

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

它很简单,但很有效。

ryoqjall

ryoqjall6#

我发现很多人使用嵌套Map,也就是说,要MapKey1 -> Key2 -> Value(我使用计算机科学/aka haskell curring符号来表示(Key1 x Key2) -> ValueMap,它有两个参数并产生一个值),首先提供第一个键--这将返回一个(partial) mapKey2 -> 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)
}

我不确定它是否比普通的复合键结构更好。你可以对此发表评论。

vhmi4jdf

vhmi4jdf7#

阅读了spaguetti/cactus堆栈之后,我想到了一个可以实现这个目的的变体,包括可以按任意顺序Map键,这样map.lookup(“a”,“b”)和map.lookup(“b”,“a”)返回相同的元素。它还可以处理任意数量的键,而不仅仅是两个。
我用它作为一个堆栈来试验数据流编程,但这里有一个快速和肮脏的版本,它作为一个多键Map(它应该得到改进:应使用集合而不是数组,以避免查找键的重复出现)

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
    }
}
hc8w905p

hc8w905p8#

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
iq0todco

iq0todco9#

我想提出两个我认为在其他答案中没有涉及的选择,它们是否对你的目的有利,你必须自己决定。

Map〈字符串,Map〈字符串,对象〉〉

您可以使用Map的Map,使用字符串1作为外部Map中的键,使用字符串2作为每个内部Map中的键。
我不认为这是一个很好的解决方案语法明智的,但它很简单,我已经看到它在一些地方使用.它也应该是有效的时间和内存,虽然这不应该是主要原因在99%的情况下.我不喜欢的是,我们已经失去了明确的信息类型的关键:它只是从代码中推断出有效键是两个字符串,读起来并不清楚。

Map〈您的对象,您的对象〉

这是一个特殊的情况。我遇到过不止一次这样的情况,所以它没有比这更特殊。如果你的对象包含两个用作键的字符串,并且基于这两个字符串定义对象相等是有意义的,那么定义equalshashCode,并将对象同时用作键和值。
在这种情况下,我们可能希望使用Set而不是Map,但是Java HashSet没有提供任何方法来从基于相等对象的集合中检索对象,所以我们确实需要map。
一个缺点是你需要创建一个新的对象来进行查找。这也适用于许多其他答案中的解决方案。

链接

Jerónimo López: Composite key in HashMaps对Map的Map效率的影响。

相关问题