Home > Software design >  Java Get maximum value of Set with attributes using a mapper
Java Get maximum value of Set with attributes using a mapper

Time:11-30

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:

  1. one loop to build the set
  2. 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 );
    }
}
  • Related