Java 使用映射器获取带有属性的集合的最大值

Java Get maximum value of Set with attributes using a mapper

我有问题。我正在尝试创建一个函数,该函数 returns Set<Measurement> 中给定属性的最大值。这是我现在拥有的功能:

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;
}

但我需要确定 maximum 不同,因为我需要在某处使用 mapper.apply() 而不是 Measurement::getMaxTemperature,因为该映射器决定我想要哪个属性的最大值的。我需要如何使用映射器?

您可以使用 lambda:

stationAverages.stream().mapToDouble(m -> mapper.apply(m)).max();

如果 mapperToDoubleFunction<Measurement>,您可以直接将其传递给 mapToDouble()。因为它不是你需要在另一个 function/lambda 中调用它,例如mapToDouble(m -> mapper.apply(m)).

或者,使用 map(mapper).max(Comparator.naturalOrder()) 获得 Optional<Double>

但是,请注意您已经有 2 个内部循环,其中一个循环不是必需的:

  1. 一个循环来构建集合
  2. 使用流的一个(隐藏)循环

你可以一次搞定:

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());

实际上,您甚至可以一次完成所有操作:

//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))
                                    )));    

这应该会提高较长时间段和更多站点的性能,因为您只是在站点上迭代一次而不是多次。复杂度应为 O(s*m) 而不是 O(y*s*m)(y = 年数,s = 站数,m = 每个站的测量数)。

为了比较,这里有一个非流式功能等价物(由于不必解压 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 );
    }
}