Java 8 Streams多个分组依据

8 java java-8 java-stream

我的温度记录是这样的

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
Run Code Online (Sandbox Code Playgroud)

我必须将其解析为POJO并根据以下问题陈述计算平均增量:

使用Streams API计算每个国家/地区的年平均温度增量.为了计算delta,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;
}
Run Code Online (Sandbox Code Playgroud)

我已经保留了一份温度列表,因为要使用流来解决这个问题.

要计算delta我试图使用以下流但我仍然无法计算实际的delta,因为我必须计算平均国家/地区delta,我已经对国家/地区,城市和日期进行了分组.

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)))));
Run Code Online (Sandbox Code Playgroud)

为了计算delta,我们必须计算差异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);
Run Code Online (Sandbox Code Playgroud)

如何一次性使用流计算增量或如何执行流操作countriesMap以计算增量并实现上述问题规则.

Nam*_*man 4

为了将问题陈述缩减为更小的块,您可以考虑的另一种方法是解析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);
Run Code Online (Sandbox Code Playgroud)

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());
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

注意:上面的代码可能缺少验证,它只是为了提供如何将完整的问题分解为更小的部分然后解决的想法。

编辑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());
Run Code Online (Sandbox Code Playgroud)

编辑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);
Run Code Online (Sandbox Code Playgroud)

  • 而不是 `AtomicReference&lt;Double&gt; cityAvgTemp = new AtomicReference&lt;&gt;((double) 0); cityMap.forEach((city,yearMap) -&gt; cityAvgTemp.set(cityAvgTemp.get() +averagePerCity(yearMap))); double avgAnnualTempDeltaPerCity = cityAvgTemp.get() / cityMap.size();`,您可以简单地使用`double avgAnnualTempDeltaPerCity = cityMap.values().stream().mapToDouble(this::averagePerCity).average();` (2认同)
  • 您也可以对外部地图重复简化;而不是 `AtomicReference&lt;Double&gt;countryValAvg = new AtomicReference&lt;&gt;(0.0); countryMap.forEach((country, cityMap) -&gt; { /* 代码不使用国家 */countryValAvg.set(countryValAvg.get() + avgAnnualTempDeltaPerCity); }) double result =countryValAvg.get() /countriesMap.size(); `,您可以简单地使用 `double result =countriesMap.values().stream().mapToDouble(cityMap -&gt; /* 用于初始化 avgAnnualTempDeltaPerCity */).average();` (2认同)