У меня есть запись температуры примерно такая
dt |AverageTemperature |AverageTemperatureUncertainty|City |Country |Latitude|Longitude
----------+-------------------+-----------------------------+-------+--------+--------+---------
1963-01-01|-5.417000000000002 |0.5 |Karachi|Pakistan|57.05N |10.33E
1963-02-01|-4.7650000000000015|0.328 |Karachi|Pakistan|57.05N |10.33E
1964-01-01|-5.417000000000002 |0.5 |Karachi|Pakistan|57.05N |10.33E
1964-02-01|-4.7650000000000015|0.328 |Karachi|Pakistan|57.05N |10.33E
1965-01-01|11.417000000000002 |0.5 |Karachi|Pakistan|57.05N |10.33E
1965-02-01|12.7650000000000015|0.328 |Karachi|Pakistan|57.05N |10.33E
Мне нужно проанализировать это в POJO и вычислить среднюю дельту в соответствии со следующей постановкой задачи:
Use the Streams API to calculate the average annual temperature delta for each country. To calculate delta the average temperature in 1900 would be subtracted from the average temperature in 1901 to obtain the delta from 1900 to 1901 for a particular city. The average of all these deltas is the average annual temperature delta for a city. The average of all cities in a country is the average of a country.
Мой умеренный POJO выглядит следующим образом с геттерами и сеттерами
public class Temperature {
private java.util.Date date;
private double averageTemperature;
private double averageTemperatureUncertainty;
private String city;
private String country;
private String latitude;
private String longitude;
}
Я вел список температур, так как эта проблема решается с помощью потоков.
Чтобы вычислить дельту, я пытаюсь использовать следующие потоки, но я все еще не могу рассчитать фактическую дельту, так как мне нужно вычислить среднюю дельту страны, я выполнил группировку по стране, городу и дате.
Map<String, Map<String, Map<Integer, Double>>> countriesMap = this.getTemperatures().stream()
.sorted(Comparator.comparing(Temperature::getDate))
.collect(Collectors.groupingBy(Temperature::getCountry,
Collectors.groupingBy(Temperature::getCity,
Collectors.groupingBy
(t -> {
Calendar calendar = Calendar.getInstance();
calendar.setTime(t.getDate());
return calendar.get(Calendar.YEAR);
},
Collectors.averagingDouble(Temperature::getAverageTemperature)))));
Чтобы вычислить дельту, нам нужно будет вычислить разницы.
для Map<Integer, Double>.
Для вычисления разницы я придумал следующий код, но не смог связать следующий код с приведенным выше.
Stream.of(10d, 20d, 10d) //this is sample data that I that I get in `Map<Integer, Double>` of countriesMap
.map(new Function<Double, Optional<Double>>() {
Optional<Double> previousValue = Optional.empty();
@Override
public Optional<Double> apply(Double current) {
Optional<Double> value = previousValue.map(previous -> current - previous);
previousValue = Optional.of(current);
return value;
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);
Как я могу рассчитать дельту, используя потоки за один раз, или как выполнить потоковые операции через countriesMap, чтобы вычислить дельту и достичь упомянутого состояния проблемы?




Чтобы сократить формулировку проблемы до меньшего блока, можно воспользоваться другим подходом, который вы можете изучить - это проанализировать температуру yearly и вычислить для них дельту, а затем ее обработать average. Однако это должно быть сделано для всех значений типа Map<Integer, Double> внутри внутреннего Map в вашем вопросе. Это выглядело бы примерно так:
Map<Integer, Double> unitOfWork = new HashMap<>(); // innermost map you've attained ('yearToAverageTemperature' map)
unitOfWork = unitOfWork.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
// the values sorted based on the year from a sorted map
List<Double> srtedValPerYear = new ArrayList<>(unitOfWork.values());
// average of deltas from the complete list
double avg = IntStream.range(0, srtedVal.size() - 1)
.mapToDouble(i -> (srtedVal.get(i + 1) - srtedVal.get(i)))
.average().orElse(Double.NaN);
Отметим далее, что это всего лишь среднее значение одной записи City для <Year, AverageTemperature>, вам придется перебирать весь ваш набор ключей City, а также весь ваш набор ключей Country, чтобы исчерпывающе определить такие средние значения.
Дальнейшее преобразование этой единицы работы в метод, перебор всей карты карт, это может быть выполнено следующим образом:
// The average of all cities in a country is the average of a country.
AtomicReference<Double> countryValAvg = new AtomicReference<>(0.0);
countriesMap.forEach((country, cityMap) -> {
// The average of all these deltas is the average annual temperature delta for a city.
AtomicReference<Double> cityAvgTemp = new AtomicReference<>((double) 0);
cityMap.forEach((city, yearMap) -> cityAvgTemp.set(cityAvgTemp.get() + averagePerCity(yearMap)));
double avgAnnualTempDeltaPerCity = cityAvgTemp.get() / cityMap.size();
countryValAvg.set(countryValAvg.get() + avgAnnualTempDeltaPerCity);
});
System.out.println(countryValAvg.get() / countriesMap.size());
где averagePerCity - метод, выполняющий следующие
double averagePerCity(Map<Integer, Double> unitOfWork) {
unitOfWork = unitOfWork.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
List<Double> srtedVal = new ArrayList<>(unitOfWork.values());
return IntStream.range(0, srtedVal.size() - 1)
.mapToDouble(i -> (srtedVal.get(i + 1) - srtedVal.get(i)))
.average().orElse(Double.NaN);
}
Примечание: В приведенном выше коде могут отсутствовать проверки, это просто для того, чтобы дать представление о том, как всю проблему можно разбить на более мелкие части, а затем решить.
Редактировать1: Какой можно было бы улучшить дальше, как:
// The average of all cities in a country is the average of a country.
AtomicReference<Double> countryValAvg = new AtomicReference<>(0.0);
countriesMap.forEach((country, cityMap) -> {
// The average of all these deltas is the average annual temperature delta for a city.
double avgAnnualTempDeltaPerCity = cityMap.values()
.stream()
.mapToDouble(Quick::averagePerCity) // Quick is my class name
.average()
.orElse(Double.NaN);
countryValAvg.set(countryValAvg.get() + avgAnnualTempDeltaPerCity);
});
System.out.println(countryValAvg.get() / countriesMap.size());
Редактировать2: И далее в
double avgAnnualTempDeltaPerCity = countriesMap.values().stream()
.mapToDouble(cityMap -> cityMap.values()
.stream()
.mapToDouble(Quick::averagePerCity) // Quick is my class name
.average()
.orElse(Double.NaN))
.average().orElse(Double.NaN);
@Holger double avgAnnualTempDeltaPerCity = cityAvgTemp.get() / cityMap.size(); Разве это не avgAnnualTempDeltaPerCountry.?
@HammadNaeem нет, дельта температуры для каждой страны оценивается вне этой итерации.
Вы можете повторить упрощение и для внешней карты; вместо AtomicReference<Double> countryValAvg = new AtomicReference<>(0.0); countriesMap.forEach((country, cityMap) -> { /* code not using country */ countryValAvg.set(countryValAvg.get() + avgAnnualTempDeltaPerCity); }) double result = countryValAvg.get() / countriesMap.size(); можно просто использовать double result = countriesMap.values().stream().mapToDouble(cityMap -> /* expression used to initialize avgAnnualTempDeltaPerCity */).average();
Вместо
AtomicReference<Double> cityAvgTemp = new AtomicReference<>((double) 0); cityMap.forEach((city, yearMap) -> cityAvgTemp.set(cityAvgTemp.get() + averagePerCity(yearMap))); double avgAnnualTempDeltaPerCity = cityAvgTemp.get() / cityMap.size();вы можете просто использоватьdouble avgAnnualTempDeltaPerCity = cityMap.values().stream().mapToDouble(this::averagePerCity).average();