Java中的双向多值映射

28

我正在寻找一种存储键值对的方法。需要进行双向查找,同时需要为同一个键存储多个值。换句话说,类似于BidiMap,但每个键可以有多个值。例如,需要能够存储如下对:"s1"->1,"s2"->1,"s3"->2,并且需要能够获取映射到每个键的值,并获取与每个值相关联的所有键。


4
你谈到了需要每个键有多个值的必要性,但在你的例子中,没有一个键有多个值,而是一个值有两个键。你可能需要澄清一下。如果你的例子符合你的问题,你会得到更好的答案;-) - pushy
在这里,您可以找到如何创建Multimap的方法:http://www.jguru.com/faq/view.jsp?EID=1317828 - maks
@pushy,同样的问题,如果我反转映射,并将整数作为键而不是值保留,我会得到一对多的映射。无论如何,感谢您的纠正。 :) - Bojana Popovska
6个回答

29

所以你需要支持多对多关系?最接近的解决方案是GuavaMultimap,就像@Mechkov写的那样 - 但更具体地说,是将MultimapMultimaps.invertFrom相结合。 "BiMultimap"尚未实现,但在Google Guava库中有一个an issue请求此功能。

此时您有几个选项:

  1. If your "BiMultimap" is going to immutable constant - use Multimaps.invertFrom and ImmutableMultimap / ImmutableListMultimap / ImmutableSetMultimap (each of theese three has different collection storing values). Some code (example taken from app I develop, uses Enums and Sets.immutableEnumSet):

    public class RolesAndServicesMapping {
        private static final ImmutableMultimap<Service, Authority> SERVICES_TO_ROLES_MAPPING = 
             ImmutableMultimap.<Service, Authority>builder()
                .put(Service.SFP1, Authority.ROLE_PREMIUM)
                .put(Service.SFP, Authority.ROLE_PREMIUM)
                .put(Service.SFE, Authority.ROLE_EXTRA)
                .put(Service.SF, Authority.ROLE_STANDARD)
                .put(Service.SK, Authority.ROLE_STANDARD)
                .put(Service.SFP1, Authority.ROLE_ADMIN)
                .put(Service.ADMIN, Authority.ROLE_ADMIN)
                .put(Service.NONE, Authority.ROLE_DENY)
                .build();
    
        // Whole magic is here:
        private static final ImmutableMultimap<Authority, Service> ROLES_TO_SERVICES_MAPPING =
                SERVICES_TO_ROLES_MAPPING.inverse();
        // before guava-11.0 it was: ImmutableMultimap.copyOf(Multimaps.invertFrom(SERVICES_TO_ROLES_MAPPING, HashMultimap.<Authority, Service>create()));
    
        public static ImmutableSet<Authority> getRoles(final Service service) {
            return Sets.immutableEnumSet(SERVICES_TO_ROLES_MAPPING.get(service));
        }
    
        public static ImmutableSet<Service> getServices(final Authority role) {
            return Sets.immutableEnumSet(ROLES_TO_SERVICES_MAPPING.get(role));
        }
    }
    
  2. If you really want your Multimap to be modifiable, it will be hard to maintain both K->V and V->K variants unless you will be modifying only kToVMultimap and call invertFrom each time you want to have its inverted copy (and making that copy unmodifiable to make sure that you accidentally don't modify vToKMultimap what wouldn't update kToVMultimap). This is not optimal but should do in this case.

  3. (Not your case probably, mentioned as bonus): BiMap interface and implementing classes has .inverse() method which gives BiMap<V, K> view from BiMap<K, V> and itself after biMap.inverse().inverse(). If this issue I mentioned before is done, it will probably have something similar.

  4. (EDIT October 2016) You can also use new graph API which will be present in Guava 20:

    As a whole, common.graph supports graphs of the following varieties:

    • directed graphs
    • undirected graphs
    • nodes and/or edges with associated values (weights, labels, etc.)
    • graphs that do/don't allow self-loops
    • graphs that do/don't allow parallel edges (graphs with parallel edges are sometimes called multigraphs)
    • graphs whose nodes/edges are insertion-ordered, sorted, or unordered

1
使用Google Guava,我们可以编写一个原始的BiMultiMap,如下所示。
import java.util.Collection;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class BiMultiMap<K,V> {

    Multimap<K, V> keyToValue = ArrayListMultimap.create();
    Multimap<V, K> valueToKey = ArrayListMultimap.create();

    public void putForce(K key, V value) {
        keyToValue.put(key, value);
        valueToKey.put(value, key);
    }

    public void put(K key, V value) {
        Collection<V> oldValue = keyToValue.get(key);
        if ( oldValue.contains(value) == false ) {
            keyToValue.put(key, value);
            valueToKey.put(value, key);
        }
    }

    public Collection<V> getValue(K key) {
        return keyToValue.get(key);
    }

    public Collection<K> getKey(V value) {
        return valueToKey.get(value);
    }

    @Override
    public String toString() {
        return "BiMultiMap [keyToValue=" + keyToValue + ", valueToKey=" + valueToKey + "]";
    }

}

希望这能对双向多映射的基本需求有所帮助。请注意,K和V需要正确实现hascode和equals方法。

1

这是一个接口。有任何实现吗? - amoebe

1

拥有两个映射,键->值,值->键,有什么问题吗?


4
我原本认为保存两份相同的数据会更容易出错。但在我查看了所有相关资料之后,我开始认为这是最佳解决方案。 - Bojana Popovska
2
只需为地图创建一个包装器,使它们保持同步即可。 - Stefan
12
我不喜欢这个答案所赞成的方法。其中可能存在许多潜在问题,包括可能会重新发明轮子、在过程中写入自己的漏洞、线程安全等等。 - bacar

-3

希望我理解得对

class A {
    long id;
    List<B> bs;
}

class B {
    long id;
    List<A> as;
}

你对这个答案太聪明了。 - Stefan Reich
我甚至记不得回答过这个问题。 - Matthias Bruns

-3

我正在使用Google的Guava MultiMap实现来达到这些目的。

Map<Key Collection<Values>> 

其中Collection可以是例如ArrayList。它允许将存储在集合中的多个值映射到一个键。

希望这有所帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接