I have a problem. I am trying to create a function that returns the maximum value of a given attribute in a Set<Measurement>
. This is the function I have right now:
public Map<Integer,Double> annualMaximumTrend(Function<Measurement,Double> mapper) {
Map<Integer, Double> maximumValue = new LinkedHashMap<>();
int startYear = determineStartYear();
for (int year = startYear; year <= LocalDate.now().getYear(); year ) {
LocalDate startDate = LocalDate.of(year - 1, 12, 31);
LocalDate endDate = LocalDate.of(year 1, 1, 1);
Set<Measurement> stationAverages = new HashSet<>();
for (Station station : stations.values()) {
stationAverages.addAll(station
.getMeasurements()
.stream()
.filter(m -> m.getDate().isAfter(startDate) &&
m.getDate().isBefore(endDate) &&
!Double.isNaN(mapper.apply(m))
).collect(Collectors.toSet()));
}
OptionalDouble maximum = stationAverages.stream().mapToDouble(Measurement::getMaxTemperature).max();
if (maximum.isPresent())
maximumValue.put(year, maximum.getAsDouble());
}
return maximumValue;
}
But I need to determine the maximum
different, because instead of Measurement::getMaxTemperature
, I need to use the mapper.apply()
somewhere, because that mapper decides which attribute I want the maximum value of. How do I need to use the mapper?
CodePudding user response:
You can use a lambda:
stationAverages.stream().mapToDouble(m -> mapper.apply(m)).max();
CodePudding user response:
If mapper
were a ToDoubleFunction<Measurement>
you could directly pass it to mapToDouble()
. Since it isn't you'd need to call it in another function/lambda, e.g. mapToDouble(m -> mapper.apply(m))
.
Alternatively, use map(mapper).max(Comparator.naturalOrder())
to get a Optional<Double>
.
However, note that you have inner 2 loops already and one of those wouldn't be necessary:
- one loop to build the set
- one (hidden) loop using the stream
You could do it all in one go:
Optional<Double> max = stations.values().stream()
.flatMap(station -> station
.getMeasurements()
.stream()
.filter(m -> m.getDate().isAfter(startDate) &&
m.getDate().isBefore(endDate)
)
.map(mapper)
.filter(v -> !Double.isNaN(v))
)
.max(Comparator.naturalOrder());
Actually, you might even be able to do everything in one go:
//stream the stations
Map<Integer, Double> maximumValue = stations.values().stream()
//flat map the stations to the measurements once
.flatMap(st -> st.getMeasurements().stream())
//filter measurements outside the time range
.filter(m -> m.getDate().getYear() >= startYear && m.getDate().getYear() <= endYear )
//filter measurements without a value
.filter(m -> !Double.isNaN(mapper.apply(m)))
//group measurements by year
.collect(Collectors.groupingBy(m->m.getDate().getYear(),
//map the grouped measurements to the value
Collectors.mapping(mapper,
//collect the max value and map it to a default value if it isn't present
//due to the filter above the default value should never be present
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.naturalOrder()),
opt -> opt.orElse(Double.MIN_VALUE))
)));
That should improve performance for longer periods and a larger amount of stations since you're only iterating over stations once instead of multiple times. Complexity should be O(s*m)
instead of O(y*s*m)
(y = number of years, s = number of stations, m = number of measurements per station).
For comparison, here's a non-streamed functional equivalent (simpler due to not having to unpack an Optional
):
Map<Integer, Double> maximumValue = new LinkedHashMap<>();
for( Station station : stations.values() ) {
for( Measurement measurement : station.getMeasurements() ) {
//1st filter
if( !(measurement.getDate().getYear() >= startYear && measurement.getDate().getYear() <= endYear )) {
continue;
}
//2nd filter
if( Double.isNaN(mapper.apply(measurement))) {
continue;
}
//put the value into the map or replace it if the other one is larger
maximumValue.merge(measurement.getDate().getYear(), mapper.apply(measurement), Math::max );
}
}