【问题标题】:Calculating average multiple numbers in a map of objects计算对象映射中的平均多个数字
【发布时间】:2014-09-22 12:44:24
【问题描述】:

我有一张格式的地图

Map<String, List<TableDTO>>

public class TableDTO {
    private String countryName;
    private String sourceName;
    private int year;
    private Double usageValue;
    private Double powerUsers;

    //Setter & Getters

}

我想找到usageValues 和powerUsers 的平均值,并且仍然保持TableDTO 结构,并且usageValue 可以为null,如果它为null,则完全忽略该对象。

<Chrome, <UK, Lorem, 2013, 2.90, 5.4>>
<Chrome, <US, Lorem, 2013, 4.10, 1.5>>
<Chrome, <EU, Lorem, 2013, 1.20, 0.22>>
<Chrome, <Asia, Lorem, 2013, 3.90, -1.10>>

<IE, <UK, Lorem, 2013, 1.40, 24.4>>
<IE, <US, Lorem, 2013, 0.90, 14.4>>
<IE, <EU, Lorem, 2013, 2.10, 0>>
<IE, <Asia, Lorem, 2013, 0.90, 0.4>>

<FF, <UK, Lorem, 2013, 0.10, 2.14>>
<FF, <US, Lorem, 2013, 1.10, 4.0>>
<FF, <EU, Lorem, 2013, , 4.4>>
<FF, <Asia, Lorem, 2013, 2.90, 4.4>>

预期结果

<1, <UK, Lorem, 2013, 1.47, 10.65>>
<2, <US, Lorem, 2013, 2.03, 6.63>>
<3, <Asia, Lorem, 2013, 2.57, 1.23>>

现在在结果中,我已经用索引替换了键,这目前很好。您会注意到,由于欧盟的 FF 具有空值,因此整个欧盟已被忽略,但对于其余部分,我计算了平均值。

如何在 Java 8 中使用 Lambda 表达式来做到这一点,还是我必须遍历?

更新 1: 这是我目前所得到的:

1. 
Map<String, List<TableDTO>> dump = mapOfAllData.values()
                .stream()
                .flatMap(list -> list.stream())
                .collect(Collectors.groupingBy(TableDTO::getCountryName));

Which give me a map with country names and the DTO orderd

2. 
  dump.values().stream().flatMap(list -> list.stream())
                .filter((o -> !o.getUsageValue().isEmpty()))
                .collect(Collectors.mapping(TableDTO::getUsageValue, Collectors.averagingDouble(Double::parseDouble)));

基本上得到平均值,但不会删除usageValue为空的DTO,我目前正在尝试解决。

更新 2:

我设法从我的地图中删除了不需要的国家。

我想弄清楚如何求两个元素的平均值,我有这个表达式

 newMap.values().stream().flatMap(list -> list.stream())
                .collect(Collectors.mapping(TableDTO::usageValue, Collectors.averagingDouble(s -> s.isEmpty() ? Double.NaN : Double.parseDouble(s))));
                       // Collectors.mapping(TableDTO::powerUsers, Collectors.averagingDouble(c -> c.isEmpty() ? Double.NaN : Double.parseDouble(c))));

但我无法获得 powerUsers 的平均值。

【问题讨论】:

  • 这与您之前的问题类似 - 如果您显示您拥有的代码以及哪些代码不起作用,会更容易。
  • @assylias,老实说,我还没有解决这个问题,我的问题是我没有任何导师帮助我学习 Java,所以我更多地依赖论坛和 Javadocs
  • @victor:特别是如果你想学习,你应该先尝试自己解决问题。在这里问几乎相同的问题表明学习并不成功,并建议您应该质疑您的学习方法。
  • 谢谢大家的支持,我明白你的意思了,给我一些时间,我会发布更新。
  • 什么是getNewIndex()

标签: java map lambda average java-8


【解决方案1】:

要理解,您想要每个 List&lt;TableDTO&gt;groupBy countryNamesourceNameyear 的平均值,但平均值在不同的字段上?

我希望 usagePowerpowerUsersDouble,而不是像您的代码和您使用 Double.parseDouble 建议的字符串。

这段代码应该可以做到:

package stackoverflow;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

public class TableDTO {
  private final String countryName;
  private final String sourceName;
  private final int year;
  @Nullable
  private final Double usageValue;
  private final Double powerUsers;

  public TableDTO(final String countryName, final String sourceName, final int year, final Double usageValue,
      final Double powerUsers) {
    this.countryName = countryName;
    this.sourceName = sourceName;
    this.year = year;
    this.usageValue = usageValue;
    this.powerUsers = powerUsers;
  }

  public String getCountryName() {return countryName;}
  public String getSourceName() {return sourceName;}
  public int getYear() {return year;}
  @Nullable public Double getUsageValue() {return usageValue;}
  public Double getPowerUsers() {return powerUsers;}

  @Override
  public String toString() {
    return "TableDTO [countryName=" + countryName + ", sourceName=" + sourceName + ", year=" + year + ", usageValue="
        + usageValue + ", powerUsers=" + powerUsers + "]";
  }

  public static void main(final String[] args) {

    final java.util.Map<String, java.util.List<TableDTO>> data = new LinkedHashMap<>();

    final List<TableDTO> chrome = new ArrayList<>();
    chrome.add(new TableDTO("UK", "Lorem", 2013, 2.90, 5.4));
    chrome.add(new TableDTO("US", "Lorem", 2013, 4.10, 1.5));
    chrome.add(new TableDTO("EU", "Lorem", 2013, 1.20, 0.22));
    chrome.add(new TableDTO("Asia", "Lorem", 2013, 3.90, -1.10));
    data.put("Chrome", chrome);

    final List<TableDTO> ie = new ArrayList<>();
    ie.add(new TableDTO("UK", "Lorem", 2013, 1.40, 24.4));
    ie.add(new TableDTO("US", "Lorem", 2013, 0.90, 14.4));
    ie.add(new TableDTO("EU", "Lorem", 2013, 2.10, 0.));
    ie.add(new TableDTO("Asia", "Lorem", 2013, 0.90, 0.4));
    data.put("IE", ie);

    final List<TableDTO> fx = new ArrayList<>();
    fx.add(new TableDTO("UK", "Lorem", 2013, 0.10, 2.14));
    fx.add(new TableDTO("US", "Lorem", 2013, 1.10, 4.0));
    fx.add(new TableDTO("EU", "Lorem", 2013, null, 4.4));
    fx.add(new TableDTO("Asia", "Lorem", 2013, 2.90, 4.4));
    data.put("FX", fx);

    data.values()
        .stream()
        .flatMap(List::stream)
        .collect(Collectors.groupingBy(dto -> Arrays.asList(dto.getCountryName(), dto.getSourceName(), dto.getYear())))
        .values()
        .stream()
        .filter(list -> list.stream().map(TableDTO::getUsageValue).noneMatch(Objects::isNull))
        .map(
            values -> {
              final TableDTO root = values.iterator().next();

              final double usageValueAvg = values.stream().map(TableDTO::getUsageValue).filter(Objects::nonNull)
                  .collect(Collectors.averagingDouble(Double::doubleValue));
              final double powerUsersAvg = values.stream().map(TableDTO::getPowerUsers)
                  .collect(Collectors.averagingDouble(Double::doubleValue));

              return new TableDTO(root.getCountryName(), root.getSourceName(), root.getYear(), usageValueAvg,
                  powerUsersAvg);

            }).forEach(System.out::println);
    ;

  }
}

结果是:

TableDTO [countryName=UK, sourceName=Lorem, year=2013, usageValue=1.4666666666666666, powerUsers=10.646666666666667]
TableDTO [countryName=US, sourceName=Lorem, year=2013, usageValue=2.033333333333333, powerUsers=6.633333333333333]
TableDTO [countryName=Asia, sourceName=Lorem, year=2013, usageValue=2.5666666666666664, powerUsers=1.2333333333333334]

还有解释:我已经拿了你的一些代码来做。

  • data 的值执行flatMap

    data.values()
        .stream()
        .flatMap(List::stream)
    
  • 通过一些键对TableDTO 进行分组:我们不关心键,唯一重要的是它正确实现了hashCodeequalsArrays.asList 完成这项工作。否则,创建一个类元组,它接受一个数组并使用Arrays.hashCode/equals

        .collect(Collectors.groupingBy(dto -> Arrays.asList(dto.getCountryName(), dto.getSourceName(), dto.getYear())))
        .values()
        .stream()
    

    由于我们不需要列表,因此我们选择值并使用流。

  • 我们过滤TableDTO,其中包含一个空的usageValue

        .filter(list -> list.stream().map(TableDTO::getUsageValue).noneMatch(Objects::isNull))
    
  • 然后我们做一张地图,在你未能找到解决方案的地方:由于该组,所有TableDTO 共享相同的countryNamesourceNameyear 值。但不是usageValuepowerUsers

    因为列表不能为空,所以我们获取第一个元素。

        .map(
            values -> {
              final TableDTO root = values.iterator().next();
    
  • 在另一个结果中,我们计算两个平均值,过滤 usageValue 的任何空值。

              final double usageValueAvg = values.stream().map(TableDTO::getUsageValue).filter(Objects::nonNull)
                  .collect(Collectors.averagingDouble(Double::doubleValue));
              final double powerUsersAvg = values.stream().map(TableDTO::getPowerUsers)
                  .collect(Collectors.averagingDouble(Double::doubleValue));
    
  • 然后我们根据三个分组键和两个平均值返回一个新的TableDTO

              return new TableDTO(root.getCountryName(), root.getSourceName(), root.getYear(), usageValueAvg,
                  powerUsersAvg);
    
            })
    
  • 然后我们打印出来,瞧! :)

          .forEach(System.out::println);
    

希望它能解决你的问题。

我在 Eclipse 中对其进行了测试,它可以编译,但是 javac 可能会失败,因为编译器与 Lambdas 的工作方式不同。

【讨论】:

  • 你的解释我现在知道我哪里出错了。最终结果的数据结构是什么。我尝试指定 Map,但出现错误,必须将其定义为 Stream,这是正确的吗?我遇到以下错误 - “这是错误错误:(87, 21)java:不兼容的类型:不存在类型变量 R 的实例,因此 java.util.stream.Stream 符合java.util.Map>"
  • 最终结果将是Stream&lt;TableDTO&gt;。您需要使用collect(Collectors.toList()) 来获得List&lt;TableDTO&gt;。如果您需要保留信息(如“Chrome”等),那么我的示例是错误的:您需要一个中间 map 操作将每个 Map.Entry&lt;String,List&lt;TableDTO&gt;&gt; 转换为 List&lt;BrowserTableDTO&gt;(假设 String 是浏览器名称 ;))。
  • 谢谢,但这是我所做的 forEach(element -> _finalRank.add((TableDTO) element));其中 _finalRank 是 List
  • List&lt;TableDTO&gt; rank = new ArrayList&lt;&gt;();forEach(rank::add),没有问题。除非你更新了我给你的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-25
相关资源
最近更新 更多