2012-01-09 20 views
22

Mam dwie mapy, których klucze to String s i których wartości to Set<MyObject>. Biorąc pod uwagę dwa typy: Map s, co jest najłatwiejszym sposobem ich scalenia, tak, że jeśli dwa klucze są identyczne, wartość jest połączeniem dwóch zestawów. Możesz założyć, że wartości nigdy nie są puste i jeśli jest użyteczne, możemy je wykonać.Scalanie dwóch map

+3

Jeśli masz możliwośc korzystania Guawy [Multimap] (https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap), można po prostu uniknąć tego problemu, a łączenie jest tak proste, jak putAll (Multimap other). – Dag

+0

podobny, prawdopodobnie duplikat: http://stackoverflow.com/questions/4299728/how-can-i-combine-two-hashmap-objects-containing-same-types –

+0

Powinny być łatwe do wykonania z [Map merge ] (https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#merge-KV-java.util.function.BiFunction-) metoda. – Roland

Odpowiedz

12

Czy mówimy o wystąpieniach HashMap. W tym przypadku odnośnikiem jest O (1), więc możesz po prostu wziąć jedną mapę, powtórzyć wpisy tej mapy, sprawdzić, czy druga mapa zawiera ten klucz. Jeśli nie, po prostu dodaj zestaw. Jeśli zawiera klucz, weź unię dwóch zestawów (przez adding all elements jednego zestawu do drugiego)

Aby zilustrować niektóre kodu, gdzie stosowano szereg mieć autouzupełnianie w moim 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

Pominie to wpisy, które istnieją w secondMap, ale nie w firstMap – sam

+0

sam - edytuje drugą mapę w miejscu, więc druga mapa zostanie zmieniona. – JFK

+0

można użyć - metoda addAll http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html Ale zawsze jest ten problem, który - jeśli twoje dwie mapy hash mają dowolny klucz taki sam - wówczas zastąpi wartość klucza z pierwszej mapy skrótu z wartością klucza z drugiej mapy skrótu. Za bezpieczniejszą stronę - zmień wartości kluczy - możesz użyć prefiksu lub sufiksu na klawiszach - (inny prefiks/sufiks dla pierwszej mapy skrótu i ​​inny prefiks/sufiks dla drugiej mapy skrótu) –

1

Poniższa powinny scalić map1 do map2 (niesprawdzone):

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

nie wiem co masz parametryzowane swoje Set s na, stąd <???>: zastąpić odpowiednio.

4

Jak na ten temat (niesprawdzone):

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

Takie rozwiązanie nie zmienia mapy wejściowych, a ponieważ jest krótki i opiera się tylko na metodach API uważam, że to dość czytelne.

Zauważ, że putAll() i addAll() są oba opcjonalne metody Map i Set. W związku z tym (i aby uzyskać wyszukiwanie O (1), polecam używanie HashMap i HashSet.

Pamiętaj, że ponieważ ani HashSet, ani HashMap nie są zsynchronizowane, musisz znaleźć inne rozwiązanie, jeśli potrzebujesz kodu bezpiecznego dla wątków.

1

Coś takiego (niesprawdzone):

// 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; 
} 

Zauważ, że to rozwiązanie nie mutują jeden z jej argumentów.

+1

Co z kluczami, które są w ' m2' ale nie w 'm1'? –

+0

Wywołujesz 'm2.getValue()', ale 'm2' jest' Mapą' i dlatego nie ma żadnej metody 'getValue()'. –

+0

@MichaelMcGowan: O tak, naprawiłem to również (geez, zobacz co się stanie, gdy spróbuję zakodować u góry mojej głowy!) – 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

To jest prosty program, który wyjaśnia jak scalić dwie mapy – user3301756

0

pamiętać, że wszystkie inne odpowiedzi będzie ostatecznie poszerzyć oryginalne zestawy których możesz nie chcieć dla wszystkich przypadków użycia, jeśli nie chcemy po prostu użyć jako wyjście trzecią mapę i utworzyć nowy zestaw dla każdego klawisza

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

można to zrobić z stream dość łatwo:

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

ten dzieli mapy do swoich Entry s, a następnie łączy je z Collector, który resolves duplicates przez dodanie obu wartości do nowego HashSet.

Działa to również dla dowolnej liczby map.

niektóre odmiany, które wytwarzają taki sam wynik:

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

Trzeci parametr Collectors.toMap nie jest konieczny, jeśli nie ma duplikatów kluczy.

Jest jeszcze jeden Collectors.toMap z czwartym parametrem, który pozwala wybrać typ Map zebrany w.

+4

Trochę bardziej zwięzły jest użycie ' Stream.concat (first.entrySet(). Stream(), second.entrySet(). Stream()) 'i unikaj' map' i 'flatMap'. –

0

Jeśli chcesz uzyskać niezmienne struktury danych, aby zapobiec manipulacji połączonymi mapami i ustawionymi instancjami mapy, możesz zastosować to podejście. To rozwiązanie korzysta z biblioteki Google Guava.

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

Jeśli zdefiniujemy metodę zjednoczenia non-zerowe Set s jako:

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

następnie łącząc dwie mapy m1 i m2 o Set<V> wartości można przeprowadzić w następujący sposób:

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

Lub jeszcze prostsze:

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); 
    }); 
} 
Powiązane problemy