Reputation: 31252
I have a Map
of the following type
public class MapUtils {
private Map<String, Integer> queryCounts = new HashMap<>();
public void averageCounters(){
int totalCounts = queryCounts.values().stream().reduce(0, Integer::sum);
queryCounts = queryCounts.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
(Map.Entry::getValue)/totalCounts
));
}
This does not compile and shows error in this line (Map.Entry::getValue)/totalCounts
. How do I fix this? Is there a better way to get achieve average over Map
using Java 8 API?
EDIT: Is this a better approach?
queryCounts.entrySet()
.forEach(entry -> queryCounts.put(entry.getKey(),
entry.getValue()/totalCounts));
Upvotes: 1
Views: 1739
Reputation: 100219
If you want in-place modification, it's much better to use Map.replaceAll
instead of Stream API:
int totalCounts = queryCounts.values().stream()
.collect(Collectors.summingInt(Integer::intValue));
queryCounts.replaceAll((k, v) -> v/totalCounts);
However in your case this solution is problematic as division results will be rounded to an int
number, thus you almost always will got zeroes in the result. Actually there's the same problem in your code. You probably want to have Map<String, Double>
as the resulting type. So you probably need to create a completely new Map
:
Map<String, Double> averages = queryCounts.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey,
e -> ((double)e.getValue())/totalCounts));
An alternative would be to have queryCounts
declared as Map<String, Double>
in the first place. This way you can use replaceAll
:
double totalCounts = queryCounts.values().stream()
.collect(Collectors.summingDouble(Double::doubleValue));
queryCounts.replaceAll((k, v) -> v/totalCounts);
Finally there's also one more alternative which is the most efficient, but dirty. Your code assumes that original (non-averaged) queryCounts
are unnecessary after averageCounters()
is called. Thus you can keep queryCounts
as Map<String, Integer>
(which is more effective than counting to Map<String, Double>
), but then change the Map
values type like this:
double totalCounts = queryCounts.values().stream()
.collect(Collectors.summingInt(Integer::intValue));
Map<String, Object> map = (Map<String, Object>)queryCounts;
map.replaceAll((k, v) -> ((Integer)v)/totalCounts);
Map<String, Double> averages = (Map<String, Double>)map;
queryCounts = null;
The similar trick is performed in JDK inside the Collectors.groupingBy
implementation.
Upvotes: 9