Java 8 Streams несколько группировок - PullRequest
0 голосов
/ 10 января 2019

у меня запись температуры примерно такая

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 и вычислить среднюю дельту согласно следующей постановке задачи:

Используйте API Streams для расчета среднегодовой дельты температуры для каждой страны. Для расчета дельты средней температуры в 1900 г. будет вычтено из средней температуры в 1901 году, чтобы получить дельта с 1900 по 1901 год для конкретного города. Среднее из всех эти дельты - среднегодовая температура для города. среднее значение по всем городам страны является средним по стране.

Мой умеренный 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, чтобы вычислить дельту и достичь упомянутого состояния проблемы .?

1 Ответ

0 голосов
/ 10 января 2019

Чтобы сократить формулировку задачи на меньший блок, вы могли бы рассмотреть еще один подход - анализ year температуры и вычисление для них дельты, а затем 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);
}

Примечание : В приведенном выше коде могут отсутствовать валидации, это просто для того, чтобы дать представление о том, как можно разбить полную проблему на более мелкие части и затем решить.

Edit1 : который может быть улучшен как :

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