2012-01-09 21 views
22

Tengo dos mapas cuyas claves son String sy cuyos valores son Set<MyObject>. Dado dos Map s, ¿cuál es la forma más fácil de combinarlos de manera que si dos claves son idénticas, el valor es una unión de los dos conjuntos. Puede suponer que los valores nunca son nulos y, si es útil, podemos hacer estos Map s SortedMap s.Fusionando dos mapas

+3

Si usted tiene la posibilidad de utilizar guayabas [Multimapa] (https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap), sólo tiene que evitar el problema, y la fusión es tan simple como putAll (Multimap otro). – Dag

+0

similares, posiblemente duplicados: http://stackoverflow.com/questions/4299728/how-can-i-combine-two-hashmap-objects-containing-the-same-types –

+0

debe ser fácil de hacer con el [Mapa de combinación ] (https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#merge-KV-java.util.function.BiFunction-) método. – Roland

Respuesta

12

Estamos hablando de HashMap instancias. En ese caso, la búsqueda es O (1), por lo que puede tomar un mapa, iterar sobre las entradas de ese mapa, ver si el otro mapa contiene esa clave. Si no, solo agrega el conjunto. Si contiene la clave, tomar la unión de los dos conjuntos (por adding all elements de un conjunto a otro)

Para ilustrar con algo de código, en el que se utiliza un conjunto para tener la terminación automática en mi IDE

Map<String, Set<Double>> firstMap = new HashMap<String, Set<Double>>(); 
Map<String, Set<Double>> secondMap = new HashMap<String, Set<Double>>(); 
Set<Map.Entry<String, Set<Double>>> entries = firstMap.entrySet(); 
for (Map.Entry<String, Set<Double>> entry : entries) { 
    Set<Double> secondMapValue = secondMap.get(entry.getKey()); 
    if (secondMapValue == null) { 
    secondMap.put(entry.getKey(), entry.getValue()); 
    } 
    else { 
    secondMapValue.addAll(entry.getValue()); 
    } 
} 
+3

Esto omitirá las entradas que existen en secondMap pero no en firstMap – sam

+0

sam - está editando el secondMap en su lugar, por lo que se cambiará secondMap. – JFK

+0

puede usar - método addAll http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html Pero siempre hay este problema que - si sus dos mapas hash tienen cualquier tecla igual - luego anulará el valor de la clave del primer mapa hash con el valor de la clave del segundo mapa hash. Para estar en el lado seguro - cambie los valores clave - puede usar prefijo o sufijo en las teclas - (prefijo/sufijo diferente para el primer hash y diferentes prefijos/sufijos para el segundo hash) –

1

lo siguiente debe combinar una map1 en map2 (no probado):

for (Entry<String, Set<???>> entry : map1.entrySet()) 
{ 
    Set<???> otherSet = map2.get(entry.getKey()); 
    if (otherSet == null) 
     map2.put(entry.getKey(), entry.getValue ()); 
    else 
     otherSet.addAll(entry.getValue()); 
} 

no sé lo que ha parametrizado sus Set s en, por lo tanto el <???>: reemplazar según sea apropiado.

4

¿Qué tal esto (no probado):

Map<String,Set<Whatever>> m1 = // input map 
Map<String,Set<Whatever>> m2 = // input map 

Map<String,Set<Whatever>> ret = // new empty map 
ret.putAll(m1); 

for(String key : m2.keySet()) { 
    if(ret.containsKey(key)) { 
     ret.get(key).addAll(m2.get(key)); 
    } else { 
     ret.put(key,m2.get(key)); 
    } 
} 

Esta solución no modifica los mapas de entrada, y porque es corto y se basa en métodos de la API solamente, me parece muy legible.

Tenga en cuenta que putAll() y addAll() son métodos opcionales en Map y Set. En consecuencia (y para obtener la búsqueda O (1)), recomendaría usar HashMap y HashSet.

Tenga en cuenta que, como ni HashSet ni HashMap están sincronizados, tendrá que buscar alguna otra solución si desea un código de seguridad de subprocesos.

1

Algo como esto (no probado):

// Assume all maps are of the same generic type. 
public static Map<String, Set<MyObject>> mergeAll(Map m1, Map m2) { 
    Map<String, Set<MyObject>> merged = new HashMap(); 
    // Merge commom entries into the new map. 
    for (Map.Entry<String, Set<MyObject>> entry : m1.entrySet()) { 
    String key = entry.getKey(); 
    Set<MyObject> s1 = new HashSet(entry.getValue()); 
    Set<MyObject> s2 = m2.get(key); 
    if (s2 != null) s1.addAll(s2); 
    merged.put(key, s1); 
    } 
    // Add entries unique to m2 to the new map. 
    for (String key : m2.keys()) { 
    if (!s1.containsKey(key)) merged.put(key, new HashSet(m2.get(key))); 
    } 
    return merged; 
} 

Tenga en cuenta que esta solución no muta cualquiera de sus argumentos.

+1

¿Qué pasa con las claves que están en ' m2' pero no en 'm1'? –

+0

Llama a 'm2.getValue()', pero 'm2' es un' Map' y por lo tanto no tiene el método 'getValue()'. –

+0

@MichaelMcGowan: Ah, claro, que fija también (! Vaya, ver lo que sucede cuando intento de codificar la parte superior de la cabeza) – maerics

0
Map<Integer,String> m1=new HashMap<Integer,String>(); 
Map<Integer,String> m2=new HashMap<Integer,String>(); 
m1.put(1,"one"); 
m1.put(2,"two"); 
m2.put(3,"three"); 
m2.put(2,"two"); 
Set<Integer> s=m2.keySet(); 
for(int i:s){ 
    if(m1.get(i)==null){ 
     m1.put(i,m2.get(i)); 
    } 
} 
System.out.println(m1); 
+0

Ésta es sencillo programa que explica cómo combinar dos mapas – user3301756

0

Tenga en cuenta que todas las demás respuestas con el tiempo aumentar los conjuntos originales que puede que no desee para todos los casos de uso, si no quiere que sólo tiene que utilizar un tercer mapa como salida y crear un nuevo conjunto para cada tecla

public static void merge2Maps(Map<String, Set<Double>> a, Map<String, Set<Double>> b, Map<String, Set<Double>> c){ 

    for (Map.Entry<String, Set<Double>> entry : a.entrySet()) { 
     Set<Double> set = new HashSet<Double>(); 
     c.put(entry.getKey(), set); 
     set.addAll(entry.getValue()); 
    } 

    for (Map.Entry<String, Set<Double>> entry : b.entrySet()) { 
     String key = entry.getKey(); 
     Set<Double> set = c.get(key); 

     if (set == null) { 
      set = new HashSet<Double>(); 
      c.put(entry.getKey(), set); 
     } 

     set.addAll(entry.getValue()); 
    } 
} 
21

puede hacer esto con un stream con bastante facilidad:

Map<T, Set<U>> merged = Stream.of(first, second) 
     .map(Map::entrySet) 
     .flatMap(Set::stream) 
     .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> { 
      HashSet<U> both = new HashSet<>(a); 
      both.addAll(b); 
      return both; 
     })); 

esto divide los mapas en sus Entry s y luego se une a ellos con un Collector que resolves duplicates agregando ambos valores a un nuevo HashSet.

Esto también funciona para cualquier cantidad de mapas.

Algunas variaciones que producen el mismo resultado:

Stream.of(first, second).flatMap(m -> m.entrySet().stream()) 
    .collect(...); 
Stream.concat(first.entrySet().stream(), second.entrySet().stream()) 
    .collect(...); //from comment by Aleksandr Dubinsky 

El tercer parámetro para Collectors.toMap no es necesario si no hay llaves duplicadas.

Hay otro Collectors.toMap con un cuarto parámetro que le permite decidir el tipo de Map recopilado.

+4

Un poco más concisa es usar ' Stream.concat (first.entrySet(). Stream(), second.entrySet(). Stream()) 'y evite' map' y 'flatMap'. –

0

Si desea terminar con las estructuras de datos inmutables para evitar la manipulación de su mapa fusionada y casos Set del mapa, entonces puede tomar este enfoque. Esta solución usa la biblioteca de Guava de Google.

public <K,T> Map<K, Set<T>> mergeToImmutable (
    final Map<K, Set<T>> left, 
    final Map<K, Set<T>> right) 
{ 
    return Maps.toMap(
     Sets.union(
      checkNotNull(left).keySet(), 
      checkNotNull(right).keySet() 
     ), 
     new Function<K, Set<T>>() { 
      @Override 
      public Set<T> apply (K input) { 
       return ImmutableSet.<T>builder() 
        .addAll(MoreObjects.firstNonNull(left.get(input), Collections.<T>emptySet())) 
        .addAll(MoreObjects.firstNonNull(right.get(input), Collections.<T>emptySet())) 
        .build(); 
      } 
     } 
    ); 
} 
0

Si se define un método para unir no nulos Set s como:

static <T> Set<T> union(Set<T>... sets) { 
    return Stream.of(sets) 
       .filter(s -> s != null) 
       .flatMap(Set::stream) 
       .collect(Collectors.toSet()); 
} 

entonces la fusión de dos mapas m1 y m2 tienen Set<V> valores se puede realizar de la siguiente manera:

Map<String, V> merged 
    = union(m1.keySet(), m2.keySet()) 
      .stream() 
      .collect(Collectors.toMap(k -> k, k -> union(m1.get(k), m2.get(k)))); 

O aún más simple:

Map<String, V> merged = new HashMap<>(); 
for (String k : union(m1.keySet(), m2.keySet()) 
    merged.put(k, union(m1.get(k), m2.get(k))); 
0
<K, V> Map<K, List<V>> mergeMapOfLists(Stream<Map<K, List<V>>> stream) { 
    return stream 
      .map(Map::entrySet) // convert each map to set of map's entries 
      .flatMap(Collection::stream) // convert each map entry to stream and flat them to one stream 
      .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, 
        (list1, list2) -> { 
         list1.addAll(list2); 
         return list1; 
        })); // convert stream to map; if key is duplicated execute merge fuction (append exisitng list with elements from new list) 
} 
4
static void mergeSet(Map<String, Set<String>> map1, Map<String, Set<String>> map2) { 
    map1.forEach((key1, value1) -> { 
     map2.merge(key1, value1, (key2, value2) -> key2).addAll(value1); 
    }); 
}