How to combine a collection of cards using streams

I have a set of cards:

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

I would like to convert this to a map

 Map<String, Double> 

so for the match key the values ​​are summed. Using a for loop, this is pretty simple:

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

and mergeMaps() is defined as

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

However, I would like to use streams to get maps from the collection. I tried the following:

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

This works without a problem. However, can I still improve it? I mean, how can I use collectors in it?

+7
java java-8 java-stream
source share
4 answers

From your input, you can get a map stream and then a flat map to have Stream<Map.Entry<String, Double>> . From there, you collect them on a new card, indicating that you want to summarize the values ​​associated with the same key.

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

Alternatively, you can use groupingBy instead of toMap :

 .collect(groupingBy(Map.Entry::getKey, summingDouble(Map.Entry::getValue))); 
+5
source share
  myCol.stream() .flatMap(x -> x.entrySet().stream()) .collect(Collectors.groupingBy( Entry::getKey, TreeMap::new, Collectors.summingDouble(Entry::getValue))); 
+2
source share

Well, the other solutions suggested show that a clean-stream solution is short, but if you want to use the existing mergeFunction (because in other cases it is more complicated, for example), you can simply pass it to Stream.reduce

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

Your initial approach with forEach is pretty much a stream loop and breaks the concept of functions without side effects. The shorthand (or higher collect) handles all data merging internally without changing the input collection.

+1
source share

With threads:

 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 

It uses Collectors.toMap to create a TreeMap result.

You can do this without threads. I think your version is a bit complicated, you can reorganize it as follows:

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

Short, easy and easy to read.

+1
source share

All Articles