2016-03-24 13 views
7

Chciałbym połączyć dwa mapa z JAVA 8 Stream:Merge Map <String, List <String> Java 8 Stream

Map<String, List<String>> mapGlobal = new HashMap<String, List<String>>(); 
Map<String, List<String>> mapAdded = new HashMap<String, List<String>>(); 

próbuję użyć tej realizacji:

mapGlobal = Stream.of(mapGlobal, mapAdded) 
       .flatMap(m -> m.entrySet().stream()) 
       .collect(Collectors.groupingBy(Map.Entry::getKey, 
         Collectors.mapping(Map.Entry::getValue,   
              Collectors.toList()) 
       )); 

Jednak ta implementacja tworzyć tylko wynik takiego:

Map<String, List<Object>>

Jeśli jeden klucz nie jest zawarty w mapGlobal, zostanie dodany jako nowy klucz z odpowiednią listą ciągów. Jeśli klucz zostanie zduplikowany pod numerami mapGlobal i mapAdded, obie listy zostaną scalone jako: A = {1, 3, 5, 7} i B = {1, 2, 4, 6}, a następnie A ∪ B = {1, 2, 3, 4, 5, 6, 7}.

Odpowiedz

7

Możesz to zrobić, powtarzając wszystkie wpisy w mapAdded i łącząc je w mapGlobal.

Następujące powtórzenia dotyczą wpisów mapAdded, wywołując forEach(action), gdzie akcja pochłania klucz i wartość każdego wpisu. Dla każdego wpisu wywołujemy merge(key, value, remappingFunction) na mapGlobal: spowoduje to utworzenie wpisu pod kluczem k i wartości v, jeśli klucz nie istnieje lub wywoła daną funkcję remapowania, jeśli już istniała. Ta funkcja przyjmuje list 2 do łączenia, które w tym przypadku są dodawane najpierw do TreeSet aby zapewnić zarówno wyjątkowy i sortowane elementy i przekształcony z powrotem do listy:

mapAdded.forEach((k, v) -> mapGlobal.merge(k, v, (v1, v2) -> { 
    Set<String> set = new TreeSet<>(v1); 
    set.addAll(v2); 
    return new ArrayList<>(set); 
})); 

Jeśli chcesz uruchomić, które potencjalnie równolegle , możesz utworzyć potok potoku, pobierając entrySet() i wywołując na nim parallelStream(). Ale musisz użyć mapy obsługującej współbieżność dla mapGlobal, np. ConcurrentHashMap.

ConcurrentMap<String, List<String>> mapGlobal = new ConcurrentHashMap<>(); 
// ... 
mapAdded.entrySet().parallelStream().forEach(e -> mapGlobal.merge(e.getKey(), e.getValue(), (v1, v2) -> { 
    Set<String> set = new TreeSet<>(v1); 
    set.addAll(v2); 
    return new ArrayList<>(set); 
})); 
+0

Ta implementacja nie będzie korzystać z ulepszeń w kolekcjach strumieni. Obie mapy mogą być ogromne, dlatego chciałbym użyć czegoś takiego jak parallelStream(). Czy to jest możliwe. – ypriverol

+2

@ypriverol Tak, mogę to edytować. – Tunaki

+2

O ile 'mapGlobal' nie jest' ConcurrentMap', mutowanie go wewnątrz strumienia równoległego nie jest bezpieczne. – Misha

1

Korzystanie z foreach przez Mapę scalania map z wartością ArrayList połączoną w wynikową mapę.

public Map<String, ArrayList<String>> merge(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2) { 
    Map<String, ArrayList<String>> map = new HashMap<>(); 
    map.putAll(map1); 

    map2.forEach((key , value) -> { 
     //Get the value for key in map. 
     ArrayList<String> list = map.get(key); 
     if (list == null) { 
      map.put(key,value); 
     } 
     else { 
      //Merge two list together 
      ArrayList<String> mergedValue = new ArrayList<>(value); 
      mergedValue.addAll(list); 
      map.put(key , mergedValue); 
     } 
    }); 
    return map; 
} 
1

Oryginalny realizacja nie tworzy efekt jak Map<String, List<Object>>, ale Map<String, List<List<String>>>. Potrzebujesz do tego dodatkowego potoku Stream, aby wyprodukować Map<String, List<String>>.