2012-01-09 42 views
22

Tôi có hai bản đồ có khóa là String và có giá trị là Set<MyObject>. Cho hai Map s, cách dễ nhất để hợp nhất chúng là gì nếu hai khóa giống nhau, giá trị là một liên kết của hai bộ. Bạn có thể giả sử các giá trị không bao giờ là rỗng và nếu nó hữu ích, chúng tôi có thể thực hiện các số Map s SortedMap s này.Hợp nhất hai Bản đồ

+3

Nếu bạn có possiblity để sử dụng ổi [Multimap] (https://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap), bạn có thể chỉ cần tránh vấn đề, và việc hợp nhất cũng đơn giản như putAll (Multimap other). – Dag

+0

tương tự, có thể trùng lặp: http://stackoverflow.com/questions/4299728/how-can-i-combine-two-hashmap-objects-containing-the-same-types –

+0

Nên dễ dàng thực hiện với [Hợp nhất bản đồ ] (https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#merge-KV-java.util.function.BiFunction-) phương pháp. – Roland

Trả lời

12

Chúng ta đang nói về các trường hợp HashMap. Trong trường hợp tra cứu đó là O (1), vì vậy bạn chỉ có thể lấy một bản đồ, lặp qua các mục của bản đồ đó, xem liệu bản đồ kia có chứa khóa đó hay không. Nếu không, chỉ cần thêm tập hợp. Nếu nó có chứa chìa khóa, lấy sự kết hợp của hai bộ (bởi adding all elements của một bộ khác)

Để minh họa với một số mã, nơi tôi đã sử dụng một Set có autocompletion trong IDE của tôi

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

Điều này sẽ bỏ qua các mục tồn tại trong secondMap nhưng không phải trong firstMap – sam

+0

sam - anh ấy đang chỉnh sửa bản đồ thứ hai tại chỗ, do đó, SecondMap sẽ được thay đổi. – JFK

+0

bạn có thể sử dụng - phương thức addAll http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html Nhưng luôn có vấn đề này - nếu hai bản đồ băm của bạn có bất kỳ khóa nào giống nhau - sau đó nó sẽ ghi đè giá trị của khóa từ bản đồ băm đầu tiên với giá trị của khóa từ bản đồ băm thứ hai. Để ở bên an toàn hơn - thay đổi giá trị khóa - bạn có thể sử dụng tiền tố hoặc hậu tố trên các phím - (tiền tố khác nhau/hậu tố cho bản đồ băm đầu tiên và tiền tố/hậu tố khác cho bản đồ băm thứ hai) –

1

sau đây nên sáp nhập một map1 vào map2 (chưa được kiểm tra):

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

tôi không biết những gì bạn đã tham số Set của bạn trên, vì thế mà <???>: thay thế cho phù hợp.

4

Làm thế nào về vấn đề này (chưa được kiểm tra):

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

Giải pháp này không sửa đổi các bản đồ đầu vào, và bởi vì nó là ngắn và dựa vào các phương pháp chỉ API, tôi thấy nó khá dễ đọc.

Lưu ý rằng putAll()addAll() là cả hai phương pháp tùy chọn trong MapSet. Do đó (và để có được O (1) tra cứu), tôi khuyên bạn nên sử dụng HashMapHashSet.

Lưu ý rằng vì không phải HashSet hoặc HashMap được đồng bộ hóa, bạn sẽ cần tìm một số giải pháp khác nếu bạn muốn mã an toàn chỉ.

1

Something như thế này (chưa được kiểm tra):

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

Lưu ý rằng giải pháp này không đột biến một trong hai đối số của nó.

+1

Điều gì về các khóa nằm trong ' m2' nhưng không phải trong 'm1'? –

+0

Bạn gọi 'm2.getValue()', nhưng 'm2' là một' Bản đồ' và do đó không có phương thức 'getValue()'. –

+0

@MichaelMcGowan: oh đúng, cố định điều đó quá (geez, xem điều gì sẽ xảy ra khi tôi cố gắng viết mã trên đỉnh đầu!) – 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

Đây là chương trình đơn giản giải thích cách hợp nhất hai bản đồ – user3301756

0

Lưu ý rằng tất cả các câu trả lời khác cuối cùng sẽ làm tăng thêm các bộ ban đầu mà bạn có thể không muốn cho tất cả các trường hợp sử dụng, nếu bạn không muốn điều đó chỉ cần sử dụng bản đồ thứ ba như sản lượng và tạo ra một bộ mới cho mỗi chủ chốt

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

Bạn có thể làm điều này với một stream khá dễ dàng:

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

này chia tách các bản đồ vào Entry s của họ và sau đó gia nhập chúng với Collectorresolves duplicates bằng cách thêm cả hai giá trị vào HashSet mới.

Điều này cũng phù hợp với bất kỳ số lượng bản đồ nào.

Một số biến thể sản xuất cùng một kết quả:

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

Tham số thứ ba cho Collectors.toMap là không cần thiết nếu không có chìa khóa trùng lặp.

Có một số khác là Collectors.toMap với thông số thứ tư cho phép bạn quyết định loại số Map được thu thập.

+4

Một chút ngắn gọn hơn là sử dụng ' Stream.concat (first.entrySet(). Stream(), second.entrySet(). Stream()) 'và tránh' map' và 'flatMap'. –

0

Nếu bạn muốn kết thúc với cấu trúc dữ liệu không thay đổi để ngăn chặn thao tác bản đồ đã hợp nhất và bản đồ Set của bản đồ thì bạn có thể thực hiện phương pháp này. Giải pháp này sử dụng thư viện ổi của 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

Nếu bạn xác định một phương pháp để đoàn kết không null Set s như:

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

sau đó sáp nhập hai bản đồ m1m2Set<V> giá trị có thể được thực hiện như sau:

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

Hoặc thậm chí đơn giản hơn:

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