【问题标题】:How to sum values within nested foreach loops?如何对嵌套的 foreach 循环中的值求和?
【发布时间】:2015-05-29 02:41:40
【问题描述】:

如何使用 stream api 和嵌套的 foreach 循环计算总和?

//java7
Double sum = null; 
for (FirstNode first : response.getFirstNodes()) {
    if (first.isValid()) {
        for (SndNnode snd : first.getSndNodes()) {
            if (snd.getType() == NodeType.AMOUNT) {
                sum += snd.getAmount();
                break;
            }
        }
    }
}

//java8
response.getFirstNodes().stream().filter(first -> first.isValid()).mapToDouble(???).sum();

我的 snd foreach 循环是:

first.getSndNodes().stream().filter(snd -> snd.getType() == NodeType.AMOUNT).mapToDouble(snd -> snd.getAmount()).findFirst().sum();

我现在如何将 snd foreach 循环集成到第一个循环中,以获得嵌套列表的全局总和?

【问题讨论】:

  • 我认为break; 语句是多余的
  • 如果getSndNodes() 返回一个非常大的列表并且我只想获得第一个/唯一的结果,则不会。但当然,这实际上只适用于非常大的列表,并且大多数时候不会产生任何影响。

标签: java foreach java-8 java-stream


【解决方案1】:

你可以使用flatMap:

response.getFirstNodes()
        .stream()
        .filter(first -> first.isValid())
        .flatMap(first -> first.getSndNodes().stream())
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .sum();

我不确定 break; 在您的原始代码中是否是故意的。


使用break; 语句,它应该如下所示:

response.getFirstNodes()
                .stream()
                .filter(first -> first.isValid())
                .map(first -> first.getSndNodes().stream().filter(snd -> snd.getType() == NodeType.AMOUNT).findFirst())
                .filter(Optional::isPresent)
                .mapToDouble(opt -> opt.get().getAmount())
                .sum();

基本上,对于每个FirstNode,您要测试它是否有效,然后将每个FirstNode 映射到其SndNodes 的流中,找到第一个类型为NodeType.AMOUNT 的流。然后,您需要过滤以仅获取非空的 Optional,并为它们获得 SndNode 它们包含的相应数量。

【讨论】:

  • break 的目的是因为我必须迭代一个大列表,并且可以确保我的条件总是只适合一个元素。所以,如果发现最内层的循环应该中断。但是从您的回答中,我认为流 api 不可能实现这种内部短路?
  • 好的,但是您的第二次 for-each 翻译与原始代码不同,因为它将汇总每个 snd 实例的所有值,即 AMOUNT 而不是原始代码中的第一个。
  • 哦,谢谢,我现在在我的帖子中添加了findFirst() 以说明问题。
【解决方案2】:

您的尝试接近正确的解决方案

response.getFirstNodes().stream()
.filter(FirstNode::isValid)
.mapToDouble(first ->
   first.getSndNodes().stream()
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .findAny().orElse(0))
.sum();

如果您确定内部流中最多有一个匹配项,则可以使用findAny,因为此时对排序没有要求。我使用最简单的解决方案来处理可能缺少匹配的问题,将其替换为0,这对sum 是透明的,并且可以避免我们进行额外的过滤。

【讨论】:

  • 这个也不错...+1
【解决方案3】:

您可以使用 flatMap 创建所有内部列表的单个 Stream :

response.getFirstNodes()
        .stream()
        .filter (first -> first.isValid())
        .flatMap (first -> first.getSndNodes().stream())
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .sum();

【讨论】:

    【解决方案4】:

    您可以使用.flatMap() 嵌套节点。例如:

    response.getFirstNodes().stream()
                            .filter(FirstNode::isValid)
                            .flatMap(first -> first.getSndNodes().stream())
                            .filter(snd -> snd.getType == NodeType.AMOUNT)
                            .mapToDouble(SndNode::getAmount)
                            .sum();
    

    【讨论】:

      猜你喜欢
      • 2018-03-12
      • 2012-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-01
      • 1970-01-01
      • 2019-07-13
      • 2016-05-28
      相关资源
      最近更新 更多