Cleaner map reduce with Java 8 and a HashMap - java

Right now I have a HashMap I'm trying to reduce to a total. Here's the current code:
HashMap map = new HashMap<String, Long>();
map.put("ex1", 757L);
map.put("ex2", 77L);
map.values()
.stream()
.reduce(0L, (a, b) -> a + b);
Is there a more elegant way to do this? I'd like to use the sum method.

You can use mapToLong to get a LongStream then call sum()
long sum = map.values().stream().mapToLong(i -> i).sum();

Related

Java 8+ Mapping a Map of Lists

I have a Map of Lists defined as such:
Map<Date,List<TimesheetContribution>> groupedByDate;
The class TimesheetContribution has a method getHours() which returns double.
What I want is:
Map<Date, Double> hoursMap = groupedByDate.entrySet().stream()...
Where the Map Values are the total hours from the TimesheetContribution instances.
The only way I can think of is something like this:
Map<Date, Double> toilAmounts = groupedByDate.entrySet().stream()
.collect(Collectors.toMap(Function.identity(), value -> ???));
As you can see, I run into trouble when attempting to define the value mapper, and I'd need a nested stream, about which I am not comfortable.
Any suggestions? Or will I have to do this the old-fashioned way?
You can do that as :
Map<Date, Double> hoursMap = groupedByDate.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, // for a key and not an entry
e -> e.getValue().stream()
.mapToDouble(TimesheetContribution::getHours)
.sum()));

Java8 HashMap inside another Hashmap, how to find salary sum

I have a structure as shown below. I am trying to calculate the sum of all employee salaries. I would like to use Java streams. Could someone please explain how I can achieve this?
Employee has a getSalary method.
Map<String, Map<String, Employee>> mainMap = new HashMap<>();
Map<String, Employee> emplmap1 = new HashMap<>();
Map<String, Employee> emplmap2 = new HashMap<>();
emplmap1.put("A",empl1);
emplmap1.put("B",empl2);
You want to use flatMap, which can accept an element that's a stream and then flattens all the streams into one stream, and a map which converts elements in a stream to different elements:
double salary = mainMap.values().stream()
.flatMap(m -> m.values().stream())
.map(Employee::getSalary)
.mapToDouble(Double::doubleValue)
.sum();
System.out.println("Total salary: " + salary);
mainMap.values().stream() will return a stream of mainMap's values (the maps). We flatMap the stream by turning every element (a map) to a stream of that map's values. Then we get the salaries, turn them into primitive doubles and finally, we sum them.
You might want to consider using the built in stats collector if you will possibly need other stats in the future.
DoubleSummaryStatistics salaryStats = mainMap.values().stream()
.flatMap(m -> m.values().stream())
.collect(Collectors.summarizingDouble(Employee::getSalary));
That way you get sum, count, max, min, average.

Return or Collect value while iterating via Hashmap using java8

Following is the traditional code to check some condition and update a variable.
HashMap<Integer,Integer> testMap= new HashMap<>();
int pair = 0;
for(Integer value: testMap.values()){
pair = pair+value/2;
}
How the same thing can be achieved using java8 streams or lambdas?
stream the Map values, transform them, then sum()
int pair = testMap.values().stream().mapToInt(i -> i / 2).sum();
To make it look a bit more like your original code, you can use a reduce() operation:
int pair = testMap.values()
.stream()
.reduce(0, (p, i) -> p + i / 2);
Basically this starts with the value 0 (the "identity") and then passes the result of applying the reduction function as input, along with the current value, to each value in turn.
P.S. program to the interface:
Map<Integer, Integer> testMap = new HashMap<>();

Java stream filter sum of values

I have a class called MonitoredData with which describes an activity, its starting time and ending time. The attributes are activityLabel, startTime, endTime. I have to group and filter these activities, using streams, the activities which have the total duration of more than 10 hours. I managed to make the sum of durations and group them according to the activity using this:
Map<String, Long> map4 = new HashMap<String, Long>();
map4 = data.stream()
.collect(
Collectors.groupingBy(
MonitoredData::getActivity,
Collectors.summingLong(MonitoredData::getDuration)
)
); //getDuration returns end Time - startTime in milliseconds
But I haven't managed to add a filter. I tried using:
.filter(Collectors.summingLong(MonitoredData::getDuration) > whatever)
but obviously it doesn't work. How can I solve this in order to make it return a Map<String, Long>?
I would first do as you've already done: I'd collect the stream of MonitoredData instances to a map, grouping by activity and summing the duration of each activity in each value:
Map<String, Long> map4 = data.stream()
.collect(Collectors.groupingBy(
MonitoredData::getActivity,
HashMap::new,
Collectors.summingLong(MonitoredData::getDuration)));
The nuance is that I'm using the overloaded version of Collectors.groupingBy that accepts a factory for the map, because in the next step I want to remove the entries whose duration is less than 10 hours, and the spec doesn't guarantee that the map returned by the Collectors.groupingBy methods that take either one or two arguments is mutable.
This is how I'd remove the entries that don't match:
public static final long TEN_HOURS_MS = 10 * 60 * 60 * 1000;
map4.values().removeIf(v -> v < TEN_HOURS_MS);
If you want to do everything in a single line, you might want to use Collectors.collectingAndThen:
Map<String, Long> map4 = data.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
MonitoredData::getActivity,
HashMap::new,
Collectors.summingLong(MonitoredData::getDuration)),
m -> { m.values().removeIf(v -> v < TEN_HOURS_MS); return m; } ));
Here I've used Collectors.collectingAndThen to modify the map returned by Collectors.groupingBy. And, within the finisher function, I've used Collection.removeIf, which takes a predicate and removes all the entries that match that predicate.
How about this? I answer on phone, you need test by yourself.
map = map.entrySet()
.stream()
.filter(it->TimeUnit.MILLISECONDS.toHours(it.getValue())>10)
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
OR using Collectors#collectingAndThen:
map4 = data.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
MonitoredData::getActivity,
Collectors.summingLong(MonitoredData::getDuration)
),
r1 -> r1.entrySet().stream()
.filter(it->TimeUnit.MILLISECONDS.toHours(it.getValue())>10)
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue))
));
I think this is what you wanted, using
Google Guava Maps class:
Map<String, Long> map = Maps.filterValues(data.stream()
.collect(Collectors.groupingBy
(MonitoredData::getActivity, Collectors.summingLong
(MonitoredData::getDuration)
)), value -> value > 3);
You can obviously write your own method to filter map like that, but since it's already there, on so popular library... How to do it with pure streams: I don't know, but maybe this will be satisfying.
Add the below code after you got the map:
map4.entrySet().stream()
.filter(a -> a.getValue() > whatever)
.collect(Collectors.joining());

Transforming list values of a map to their mean value by an attribute

I start with Map<String,List<Rating>>. Rating has a method int getValue().
I want to end up with Map<String,Integer> where the Integer value is the mean value of all the Rating.getValue() values grouped by the key from the original Map<String,List<Rating>>.
I would be pleased to receive some ideas on how to tackle this.
Performing aggregation operations on a collection of integers can be done with IntStream methods. In your case, average seems like the right method to use (notice that it returns a Double, not Integer, which seems like a better choice).
What you want is to convert each entry of the original map to an entry in a new map, where the key remains the same, and the value is the average of the values of the List<Rating> elements. Generating the output map can be done using a toMap Collector.
Map<String,Double> means =
inputMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e->e.getValue()
.stream()
.mapToInt(Rating::getValue)
.average()
.orElse(0.0)));
It can be done using averagingInt as next:
Map<String, Double> means =
map.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().collect(
Collectors.averagingInt(Rating::getValue)
)
)
);
Assuming that you would like to go a little bit further and you need more statistics like count, sum, min, max and average, you could consider using summarizingInt instead, you will then get IntSummaryStatistics instead of a Double
Map<String, IntSummaryStatistics> stats =
map.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().collect(
Collectors.summarizingInt(Rating::getValue)
)
)
);

Categories

Resources