Двунаправленная многозначная карта на Java - PullRequest
19 голосов
/ 09 ноября 2011

Я ищу способ хранения пар ключ-значение. Мне нужно, чтобы поиск был двунаправленным, но в то же время мне нужно хранить несколько значений для одного ключа. Другими словами, что-то вроде BidiMap, но для каждого ключа может быть несколько значений. Например, он должен иметь возможность хранить пары типа: «s1» -> 1, «s2» -> 1, «s3» -> 2, и мне нужно иметь возможность получить значение, сопоставленное каждому ключу, и для каждого значения получите все ключи, связанные с ним.

Ответы [ 6 ]

20 голосов
/ 09 декабря 2011

Итак, вам нужна поддержка отношений «многие ко многим»?Самое близкое, что вы можете получить, это Гуава Multimap, как писал @Mechkov - но более конкретно Multimap комбинация с Multimaps.invertFrom.«BiMultimap» еще не реализовано, но существует проблема , запрашивающая эту функцию в библиотеке Google Guava.

На данный момент у вас есть несколько вариантов:

  1. Если ваше «BiMultimap» будет иметь неизменяемую константу - используйте Multimaps.invertFrom и ImmutableMultimap / ImmutableListMultimap / ImmutableSetMultimap (каждый из этих трех имеет разные значения хранения коллекции).Некоторый код (пример взят из приложения, которое я разрабатываю, использует Enum s и 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. Если вы действительно хотите, чтобы ваша Multimap была модифицируемой, это будет трудносохранить оба варианта K-> V и V-> K, если только вы не будете изменять только kToVMultimap и вызывать invertFrom каждый раз, когда вы хотите получить его инвертированную копию (и сделать эту копию неизменяемой, чтобы убедиться, что вы случайно не 'изменить vToKMultimap что не будет обновляться kToVMultimap).Это не оптимально, но в данном случае это следует делать.

  3. (Вероятно, это не ваш случай, упомянутый в качестве бонуса): BiMap интерфейс и реализующие классы имеют .inverse() метод, который дает BiMap<V, K> вид из BiMap<K, V> и сам после biMap.inverse().inverse().Если эта проблема , о которой я упоминал ранее, будет решена, вероятно, она будет иметь нечто подобное.

  4. (РЕДАКТИРОВАТЬ в октябре 2016 г.) Вы также можете использовать новый граф API , который будет присутствовать в гуаве 20 :

    В целом, common.graph поддерживает графы следующих разновидностей:

    • ориентированные графы
    • неориентированные графы
    • узлы и / или ребра с соответствующими значениями (весами, метками и т. Д.)
    • графы, которые позволяют / не допускаютself-loop
    • графы, которые допускают / не допускают параллельные ребра (графы с параллельными ребрами иногда называют мультиграфами)
    • графы, узлы / ребра которых упорядочены по вставке, отсортированы или неупорядочены
1 голос
/ 04 октября 2016

Используя Google Guava, мы можем написать примитив BiMulitMap, как показано ниже.

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 голос
/ 06 февраля 2014

Я надеюсь, что использование MultivaluedMap решит проблему.Пожалуйста, найдите документацию от оракула по ссылке ниже.

http://docs.oracle.com/javaee/6/api/javax/ws/rs/core/MultivaluedMap.html

1 голос
/ 09 ноября 2011

Что не так с двумя картами, ключ-> значения, значения-> ключи?

0 голосов
/ 09 ноября 2011

Я использую Google Guava MultiMap для этих целей.

Map<Key Collection<Values>> 

, где Collection может быть, например, ArrayList.Это позволяет сопоставить несколько значений, хранящихся в коллекции, с ключом.Надеюсь, это поможет!

0 голосов
/ 09 ноября 2011

Надеюсь, я правильно поняла

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

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