2017-11-16 2 views
7

Ich habe eine Sammlung von Karten:wie eine Sammlung von Karten fusionieren Streams mit

Collection<Map<String,Double>> myCol = table.values(); 

Ich mag diese in eine Karte

Map<String, Double> 

, so dass für einen passenden Schlüssel zu transformieren, Werte werden aufsummiert. Mit einem for-Schleife ist es ziemlich einfach:

Map<String, Double> outMap = new HashMap<>(); 
for (Map<String, Double> map : myCol) { 
    outMap = mergeMaps(outMap, map); 
} 

und mergeMaps() als

definiert sind
mergeMaps(Map<String, Double> m1, Map<String, Double> m2){  
    Map<String, Double> outMap = new TreeMap<>(m1); 
    m2.forEach((k,v) -> outMap.merge(k,v,Double::sum)); /*sum values if key exists*/ 
    return outMap; 
} 

Allerdings würde Ich mag Strom verwenden, um eine Karte aus der Sammlung zu bekommen. Ich habe es wie folgt versucht:

Map<String, Double> outMap = new HashMap<>();  
myCol.stream().forEach(e-> outMap.putAll(mergeMaps(outMap,e))); 
return outMap; 

Dies funktioniert ohne ein Problem. Kann ich es trotzdem verbessern? Ich meine, wie kann ich Sammler darin verwenden?

+0

wahrscheinlich, etwas ähnlich -> https://stackoverflow.com/questions/34325389/java-listmapstring-long-sum-of-group-by-of-map-key – novice

Antwort

5

Von Ihrem Eingang können Sie den Strom von Karten holen und dann einen Stream<Map.Entry<String, Double>> haben flatmap. Von dort sammeln Sie sie in einer neuen Karte und geben an, dass Sie die Werte, die demselben Schlüssel zugeordnet sind, summieren möchten.

import static java.util.stream.Collectors.groupingBy; 
import static java.util.stream.Collectors.summingDouble; 
import static java.util.stream.Collectors.toMap; 

.... 

Map<String, Double> outMap = 
    myCol.stream() 
     .flatMap(m -> m.entrySet().stream()) 
     .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, Double::sum)); 

Alternativ können Sie groupingBy statt toMap verwenden:

.collect(groupingBy(Map.Entry::getKey, summingDouble(Map.Entry::getValue))); 
+2

Könnten Sie die Vor- und Nachteile von 'toMap' und' groupingBy'? Gibt es grundlegende Unterschiede in der Leistung? Aus Sicht der Lesbarkeit würde ich den Ansatz "groupingBy" bevorzugen, aber das ist nur meine Meinung. –

+1

@MalteHartwig In diesem Anwendungsfall erwarte ich keine großen Leistungsunterschiede (wieder werden nur die richtigen Messungen die Wahrheit sagen).Der Vorteil von "groupingBy" besteht darin, dass Sie Downstream-Kollektoren verketten können, wenn Sie komplexere Reduktionen benötigen und wie Sie sagen, dass es aussagekräftiger ist. –

2
myCol.stream() 
     .flatMap(x -> x.entrySet().stream()) 
     .collect(Collectors.groupingBy(
       Entry::getKey, 
       TreeMap::new, 
       Collectors.summingDouble(Entry::getValue))); 
1

Nun, die anderen vorgeschlagenen Lösungen zeigen, dass eine reine Strom Lösung ist kurz, aber wenn Sie wollen Ihre vorhandene mergeFunction verwenden (da in anderen Fällen komplexer zum Beispiel) ist, könnten Sie geben ihm nur über Stream.reduce:

Optional<Map<String, Double>> outMap = myCol.stream().reduce((m1, m2) -> mergeMaps(m1, m2)); 

Ihren ersten Ansatz mit Das forEach ist ziemlich streamyfied for-Schleife und verletzt das Konzept der Funktionen, die keine Nebenwirkungen haben. Das Reduzieren (oder das Obige sammelt) verarbeitet alle Daten, die intern zusammengeführt werden, ohne die Eingabesammlung zu ändern.

+0

Hallo! Bei Funktionen mit Nebenwirkungen ist nichts falsch. Sie werden nicht empfohlen, wenn sie in Streams verwendet werden, aber es ist absolut richtig, sie in 'forEach' zu verwenden. Auf der anderen Seite erstellt Ihre Lösung mit "Reduzieren" bei jedem Schritt eine neue Karte, die möglicherweise nicht die beste ist. –

+1

Jetzt, wo ich genauer hinschaue, hat die ursprüngliche Frage das gleiche Problem: Für jedes zu migrierende Kartenpaar wird eine neue Zwischenkarte erstellt. –

+0

True, aber Sie können das verhindern, indem Sie die 'reduce' mit identity verwenden, und die' merge' Methode verwendet die erste map als Ergebnis. Hat auch den Vorteil eines garantierten Rückgabewertes. –

1

Mit Streams:

Map<String, Double> outMap = myCol.stream() 
    .flatMap(map -> map.entrySet().stream()) 
    .collect(Collectors.toMap(
     Map.Entry::getKey, // key of the result map 
     Map.Entry::getValue, // value of the result map 
     Double::sum,   // how to merge values for equal keys 
     TreeMap::new));  // the type of map to be created 

Dies nutzt Collectors.toMap das Ergebnis TreeMap zu erstellen.

Sie können es ohne Ströme tun, though. Ich denke, Ihre Version ein wenig kompliziert ist, man könnte es Refactoring wie folgt:

Map<String, Double> outMap = TreeMap<>(); 
myCol.forEach(map -> map.forEach((k, v) -> outMap.merge(k, v, Double::sum))); 

kürzer ist, einfach und die meisten lesbar.

Verwandte Themen