Could you help me with Java Streams
?
As you can see from the title I need to merge List<Map<String, Map<String, Genuineness>>>
into Map<String, Map<String, Genuineness>>
.
The list is represented as List<Map<String, Map<String, Genuineness>>>
and looks like:
[
{
"USER_1":{
"APP_1":{
"total":1,
"totalGenuine":1,
"totalDevelopment":1
}
},
"USER_2":{
"APP_1":{
"total":1,
"totalGenuine":1,
"totalDevelopment":1
},
"APP_2":{
"total":2,
"totalGenuine":2,
"totalDevelopment":2
}
}
},
{
"USER_1":{
"APP_1":{
"total":1,
"totalGenuine":1,
"totalDevelopment":1
}
},
"USER_2":{
"APP_1":{
"total":1,
"totalGenuine":1,
"totalDevelopment":1
},
"APP_2":{
"total":2,
"totalGenuine":2,
"totalDevelopment":2
}
}
}
]
So, as you can see, duplicate keys could be everywhere.
My goal is to combine them into Map<String, Map<String, Genuineness>>
by merging Genuineness
. Merge Genuineness
simply means return a new object with summed up values total
, totalGenuine
, and totalDevelopment
.
Here is my implementation that fails:
final Map<String, Map<String, Genuineness>> map = taskHandles.stream().map(this::mapTaskHandle)
.flatMap(m -> m.entrySet().stream()).collect(
Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
.collect(
Collectors.toMap(Map.Entry::getKey,
g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
(g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
g1.getTotalGenuine() + g2.getTotalGenuine(),
g1.getTotalDevelopment() + g2.getTotalGenuine()
)
)
)
)
);
It fails with message:
java.lang.IllegalStateException: Duplicate key {TEST_33_33_APP_1=live.attach.billing.domain.model.billing.Genuineness@951b6fe}
So, seems that in my implementation I've pointed how to combine inner map but didn't make a merging of values of outer map and I don't know how to do it.
I greatly appreciate your help. Thank you in advance!
UPDATE: Expected output:
{
"USER_1":{
"APP_1":{
"total":2,
"totalGenuine":2,
"totalDevelopment":2
}
},
"USER_2":{
"APP_1":{
"total":2,
"totalGenuine":2,
"totalDevelopment":2
},
"APP_2":{
"total":4,
"totalGenuine":4,
"totalDevelopment":4
}
}
}
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.
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.
Honestly, this is a horrible data structure to work with and the maintainers of this code will have a hard time finding out problems that arise.
You should take a step back and consider refactoring the code, any way you can solve the missing part by using the following merge function in the outmost toMap
:
(l, r) -> {
r.forEach((k, v) -> l.merge(k, v,
(bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
bi.getTotalGenuine() + bii.getTotalGenuine(),
bi.getTotalDevelopment() + bii.getTotalGenuine())));
return l;
}
Full code:
taskHandles.stream().map(this::mapTaskHandle)
.flatMap(m -> m.entrySet().stream()).collect(
Collectors.toMap(Map.Entry::getKey, e -> e.getValue().entrySet().stream()
.collect(
Collectors.toMap(Map.Entry::getKey,
g -> new Genuineness(g.getValue().getTotal(), g.getValue().getTotalGenuine(), g.getValue().getTotalDevelopment()),
(g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
g1.getTotalGenuine() + g2.getTotalGenuine(),
g1.getTotalDevelopment() + g2.getTotalGenuine()
)
)
),(l, r) -> {
r.forEach((k, v) -> l.merge(k, v,
(bi, bii) -> new Genuineness(bi.getTotal() + bii.getTotal(),
bi.getTotalGenuine() + bii.getTotalGenuine(),
bi.getTotalDevelopment() + bii.getTotalGenuine())));
return l;
}
)
);
Ideone
Though Aomine's suggested solution seems to be correct, you can alternatively improve your code readability and simplify it defining a BinaryOperator<Genuineness>
as :
BinaryOperator<Genuineness> remappingGenuineness = (g1, g2) -> new Genuineness(g1.getTotal() + g2.getTotal(),
g1.getTotalGenuine() + g2.getTotalGenuine(),
g1.getTotalDevelopment() + g2.getTotalGenuine()
);
and then further use it as :
final Map<String, Map<String, Genuineness>> map = taskHandles.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream().collect(
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, remappingGenuineness)),
(a, b) -> {
a.forEach((k, v) -> b.merge(k, v, remappingGenuineness));
return b;
}));
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