I have a list of maps that looks something like this:
[
{
"name": "A",
"old": 0.25,
"new": 0.3
},
{
"name": "B",
"old": 0.3,
"new": 0.35
},
{
"name": "A",
"old": 0.75,
"new": 0.7
},
{
"name": "B",
"old": 0.7,
"new": 0.60
}
]
and I want the output to look like this:
{
"A": {
"old": 1,
"new": 1
},
"B": {
"old": 1,
"new": 0.95
}
}
...where the values of old
and new
are summed for each related entry.
The data type of the list of maps is List<Map<String, Object>>
, so the output should be a Map<String, Map<String, Double>>
.
With some diagram drawing, documentation reading, and trial and error, I was able to come up with this:
data.stream()
.collect(
Collectors.groupingBy(entry -> entry.get("name"),
Collectors.summingDouble(entry ->
Double.parseDouble(entry.get("old").toString())))
);
to produce an object of type Map<String, Double>
, where the output is
{
"A": 1,
"B": 1
}
for the summations of the old
values. However, I can't quite transform it into a map of maps. Something like this:
data.stream()
.collect(
Collectors.groupingBy(entry -> entry.get("name"),
Collectors.mapping(
Collectors.groupingBy(entry -> entry.get("old"),
Collectors.summingDouble(entry ->
Double.parseDouble(entry.get("old").toString())
)
),
Collectors.groupingBy(entry -> entry.get("new"),
Collectors.summingDouble(entry ->
Double.parseDouble(entry.get("new").toString())
)
)
)
)
);
doesn't work, because Collectors.mapping()
only takes one mapping function and a downstream collector, but I'm not sure how to map two values at once.
Is there another function I need to create mappings of two different values? Any suggestions on better ways of doing this is greatly appreciated as well.
Method 1: Using Collectors.toMap() Function The Collectors. toMap() method takes two parameters as the input: KeyMapper: This function is used for extracting keys of the Map from stream value. ValueMapper: This function used for extracting the values of the map for the given key.
Java 8 Stream's map method is intermediate operation and consumes single element forom input Stream and produces single element to output Stream. It simply used to convert Stream of one type to another. Let's see method signature of Stream's map method.
Converting complete Map<Key, Value> into Stream: This can be done with the help of Map. entrySet() method which returns a Set view of the mappings contained in this map. In Java 8, this returned set can be easily converted into a Stream of key-value pairs using Set. stream() method.
You can use streams, but you can also use Map
's computeIfAbsent
and merge
methods:
Map<String, Map<String, Double>> result = new LinkedHashMap<>();
data.forEach(entry -> {
String name = (String) entry.get("name");
Map<String, Double> map = result.computeIfAbsent(name, k -> new HashMap<>());
map.merge("old", (Double) entry.get("old"), Double::sum);
map.merge("new", (Double) entry.get("new"), Double::sum);
});
It is possible to achieve this only using Stream
tools (similar to this):
Map<String, Map<String, Double>> collect = data.stream().collect(
Collectors.groupingBy(m -> (String)m.get("name"),
Collector.of(LinkedHashMap::new,
(acc, e) -> Stream.of("old", "new").forEach(key -> acc.merge(key, (Double) e.get(key), Double::sum)),
(m1, m2) -> {
m2.forEach((k, v) -> m1.merge(k, v, Double::sum));
return m1;
})
));
There is also the > Java 8 way:
Map<String, Map<String, Double>> stats = data.stream().collect(
Collectors.groupingBy(m -> (String) m.get("name"),
Collectors.flatMapping(m -> m.entrySet().stream().filter(e -> !"name".equals(e.getKey())),
Collectors.toMap(Map.Entry::getKey, e -> (Double)e.getValue(), Double::sum, LinkedHashMap::new)
)
));
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