I have a class Values:
public class Values {
private int count;
private int values;
}
And a list of multiple maps of type Map<String, Values>
Map<String, Values> map1 = new HashMap<>();
map1 .put("aaa", new Values(1, 10));
map1 .put("bbb", new Values(5, 50));
map1 .put("ccc", new Values(2, 30));
Map<String, Values> map2= new HashMap<>();
map2.put("aaa", new Values(2, 20));
map2.put("bbb", new Values(3, 50));
map2.put("ccc", new Values(3, 10));
List<Map<String, Values>> list = Arrays.asList(map1, map2);
There could be any number of maps and any number of entries inside the maps, but the keys for the maps are always the same, only the values can differ. My example contains only 2 maps and only 3 entries for each map for clarity.
I want to obtain a single map with the same keys and with the Values objects as the sum of each "count" and each "value" in the objects, like this:
{
aaa= {
count = 3,
value = 30
},
bbb= {
count = 8,
value = 100
},
ccc= {
count = 6,
value = 40
}
}
I am trying to achieve this using the Streams API, but I am stuck:
public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
return maps.stream()
.flatMap(m -> m.entrySet().stream()) ...?
}
How can I group each entry by their keys and add up each count and each value into a single map?
Thank you in advance.
forEach((k, v) -> mapGlobal. merge(k, v, (v1, v2) -> { Set<String> set = new TreeSet<>(v1); set. addAll(v2); return new ArrayList<>(set); })); If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it.
concat() Alternatively, we can use Stream#concat() function to merge the maps together. This function can combine two different streams into one. As shown in the snippet, we are passed the streams of map1 and map2 to the concate() function and then collected the stream of their combined entry elements.
Java HashMap merge() The Java HashMap merge() method inserts the specified key/value mapping to the hashmap if the specified key is already not present. If the specified key is already associated with a value, the method replaces the old value with the result of the specified function.
To get the sum of all values in a Map : Use the forEach() method to iterate over the Map . On each iteration, add the number to the sum , reassigning the variable.
You can collect the entries of your Stream
with a toMap
collector, with a merge function.
public static Map<String, Values> mergeMaps(List<Map<String, Values>> maps) {
return maps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(v1,v2) -> new Values(v1,v2)));
}
Assuming you have a Values
constructor that takes two Values
instances and creates an instance having the sums of the values.
Of course, you can write the merge function without that constructor. For example:
(v1,v2) -> new Values(v1.getCount()+v2.getCount(),v1.getValue()+v2.getValue())
One more solution with groupingBy:
Map<String, Optional<Values>> collect = list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue,
reducing((v1, v2) -> new Values(v1.count + v2.count, v1.values + v2.values)))));
Note: values of this map are Optional<Values>
.
If you have null
value on one of your source map like map2.put("ddd", null);
it allows to avoid NullPointerException
and return Optional.empty
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With