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();
如果 mapper
是 ToDoubleFunction<Measurement>
,您可以直接将其传递给 mapToDouble()
。因为它不是你需要在另一个 function/lambda 中调用它,例如mapToDouble(m -> mapper.apply(m))
.
或者,使用 map(mapper).max(Comparator.naturalOrder())
获得 Optional<Double>
。
但是,请注意您已经有 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 );
}
}
我有问题。我正在尝试创建一个函数,该函数 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();
如果 mapper
是 ToDoubleFunction<Measurement>
,您可以直接将其传递给 mapToDouble()
。因为它不是你需要在另一个 function/lambda 中调用它,例如mapToDouble(m -> mapper.apply(m))
.
或者,使用 map(mapper).max(Comparator.naturalOrder())
获得 Optional<Double>
。
但是,请注意您已经有 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 );
}
}