Java是否具有带有反向查找的HashMap?


98

我有以“键-键”格式而不是“键-值”格式组织的数据。这就像一个HashMap,但是我将需要在两个方向上进行O(1)查找。这种数据结构是否有名称,Java的标准库中是否包含类似的名称?(或者Apache Commons?)

我可以编写自己的类,该类基本上使用两个镜像的Map,但我不想重蹈覆辙(如果已经存在,但我只是没有在寻找正确的术语)。

Answers:


106

Java API中没有此类。您想要的Apache Commons类将成为BidiMap的实现之一。

作为数学家,我将这种结构称为双射。


83
作为一个非数学家,我会称这种结构的“地图是什么使您可以通过按键或周围的其他方式查找值”
多纳尔

4
遗憾的是,它不支持泛型,而Guava似乎支持。
伊朗棉兰2012年


1
@Don“ Bidi”->“ Bi-Directional”
ryvantage

3
@Dónal是的,但是整个IT都是基于数学的
Alex

75

除了Apache Commons外,Guava还具有BiMap


谢谢(你的)信息!我暂时坚持使用Apache(除非有一些很好的理由不这样做?)
Kip

我无法提供与apache集合的良好比较,但是google集合确实有很多不错的东西,我认为值得一看。
ColinD

16
Google收藏集的优势之一是它具有泛型,而Commons Collections没有。
标记

3
要比较这两个库,请参见此答案中的引号:stackoverflow.com/questions/787446/…(和原始采访)。出于明显的原因,这偏向Google,但即使如此,我仍然可以肯定地说,如今使用Google Collections更好。
约尼克,

1
BiMap链接已损坏。请使用这个
Mahsa2,2016年

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

11

如果没有发生碰撞,则始终可以将两个方向都添加到同一HashMap中:-)


6
@Kip:为什么?在某些情况下,这是一个完全合法的解决方案。因此将具有两个哈希图。
劳伦斯·多尔

7
不,这是一个丑陋,脆弱的hack。它要求在每个get()和put()上都维护双向属性,并且可以将其传递给其他修改地图的方法,而无需了解双向属性。也许可以将其作为未传递到任何地方的方法中的局部变量,或者在创建后立即将其设置为不可修改。但即便如此,它还是脆弱的(有人出现并进行调整并以某种方式破坏双向性,而这种方式不会总是立即显示出自己是一个问题)
Kip

1
@Kip,我同意应该使用该映射将此类用法保留在类的内部,但只有当相应的JUnit测试草率时,您的最后一句话才成立:-)
rsp

如果我可以非常有效地使用这种实现,请想象在这里需要一个映射来对汇编语言指令的操作码进行解码/编码,您也永远不会更改映射的状态,在一个方向上,键是指令字符串,另一个是二进制值。因此,您永远不应该有冲突。
MK 2013年

为了进行小规模查找,该hack解决了我的问题。
milkersarac

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

GETah的回答启发,我决定自己编写一些类似的东西并进行一些改进:

  • 该类正在实现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;
        }
    }

}

请注意,就像BiMap和BidiMap一样,这是一个双射,不允许具有相同值的多个键。(getReverseView()。get(v)将始终仅返回一个键)。
Donatello

的确如此,但是OTOH正是OP所要求的
Qw3ry

我不确定他是否表示其数据符合此约束,但是无论如何,它可以帮助其他人更好地理解!
Donatello

0

这里有一个很老的问题,但是如果其他人像我刚遇到的那样阻碍并偶然发现这一点,希望这会有所帮助。

我也一直在寻找双向HashMap,有时它是最简单的答案,也是最有用的。

如果您不想重新发明轮子,又不想在项目中添加其他库或项目,那么如何简单地实现并行数组(如果您的设计需要ArrayLists)就可以了。

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

一旦知道两个键中的1个的索引,就可以轻松请求另一个。因此,您的查找方法可能类似于:

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

这是假设您使用的是正确的面向对象的结构,其中只有方法才能修改这些array / ArrayLists,使它们保持并行非常简单。对于ArrayList来说甚至更容易,因为只要数组大小改变就不必重建,只要您串联添加/删除即可。


3
您正在丢失HashMaps的一个重要功能,即O(1)查找。这样的实现需要扫描一个数组,直到找到要查找的项的索引,即O(n)
Kip 2015年

是的,这是事实,这是一个很大的缺点。但是,就我个人而言,我实际上是在需要一个三向键列表,并且我总是会提前至少知道其中一个键,因此对我个人而言这不是问题。不过,感谢您指出这一点,我似乎在我的原始帖子中忽略了这一重要事实。
ThatOneGuy 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.