【问题标题】:Java 8 stream Map<String, List<String>> sum of values for each keyJava 8 流 Map<String, List<String>> 每个键的值的总和
【发布时间】:2018-12-27 22:28:27
【问题描述】:

我对 Java 8 不太熟悉(仍在学习中),并希望看看是否可以使用流找到与以下代码等效的内容。

下面的代码主要是尝试获取String中每个值对应的double值,然后求和。我在这种格式的任何地方都找不到太多帮助。我不确定使用流是否会清理代码或使其更混乱。

// safe assumptions - String/List (Key/Value) cannot be null or empty
// inputMap --> Map<String, List<String>>

Map<String, Double> finalResult = new HashMap<>();
for (Map.Entry<String, List<String>> entry : inputMap.entrySet()) {
    Double score = 0.0;
    for (String current: entry.getValue()) {
        score += computeScore(current);
    }
    finalResult.put(entry.getKey(), score);
}

private Double computeScore(String a) { .. }

【问题讨论】:

    标签: java list dictionary java-8 java-stream


    【解决方案1】:
    Map<String, Double> finalResult = inputMap.entrySet()
            .stream()
            .collect(Collectors.toMap(
                    Entry::getKey,
                    e -> e.getValue()
                          .stream()
                          .mapToDouble(str -> computeScore(str))
                          .sum()));
    

    上面的代码遍历映射并创建一个具有相同键的新映射,在放置值之前,它首先遍历每个值 - 这是一个列表,通过调用 computeScore() 对每个列表元素计算分数,然后将收集到的分数放入值中。

    【讨论】:

    • 请为您所做的添加一些解释,并格式化您的代码,使其易于阅读。
    • 代码已经格式化。如果我错过了,你能指出来吗?
    • @PankajSinghal 格式化了一下,请在代码中添加一些解释。
    • 添加说明
    【解决方案2】:

    您还可以将forEach 方法与流API 一起使用来产生您正在寻找的结果。

    Map<String, Double> resultSet = new HashMap<>();
    inputMap.forEach((k, v) -> resultSet.put(k, v.stream()
                .mapToDouble(s -> computeScore(s)).sum()));
    

    s -&gt; computeScore(s) 可以更改为使用方法引用,即T::computeScore,其中T 是包含computeScore 的类的名称。

    【讨论】:

      【解决方案3】:

      这个怎么样:

      Map<String, Double> finalResult = inputMap.entrySet()
          .stream()
          .map(entry -> new AbstractMap.SimpleEntry<String, Double>(   // maps each key to a new
                                                                       // Entry<String, Double>
              entry.getKey(),                                          // the same key
              entry.getValue().stream()                             
                  .mapToDouble(string -> computeScore(string)).sum())) // List<String> mapped to 
                                                                       // List<Double> and summed
          .collect(Collectors.toMap(Entry::getKey, Entry::getValue));  // collected by the same 
                                                                       // key and a newly 
                                                                       // calulcated value
      

      上面的版本可以合并到单一的collect(..)方法:

      Map<String, Double> finalResult = inputMap.entrySet()
          .stream()
          .collect(Collectors.toMap(
               Entry::getKey,                        // keeps the same key
               entry -> entry.getValue()
                             .stream()               // List<String> -> Stream<String>
                                                     // then Stream<String> -> Stream<Double>
                             .mapToDouble(string -> computeScore(string)) 
                             .sum()));               // and summed 
      

      关键部分:

      • collect(..) 使用带有Collector 的特定策略对元素进行缩减。
      • Entry::getKeyentry -&gt; entry.getKey 的快捷方式。映射键的函数。
      • entry -&gt; entry.getValue().stream() 返回 Stream&lt;String&gt;
      • mapToDouble(..) 返回 DoubleStream。这有一个聚合操作 sum(..) 对元素求和 - 一起为 Map 创建一个新值。

      【讨论】:

      • 似乎应该可以工作,但有点乱。我们可以使用 summingDouble 来简化吗?
      • 我已经对其进行了改造,提供了替代解决方案并进行了详细描述。
      • 我会避免第一种方法,因为它不必要地创建 AbstractMap.SimpleEntry 对象。但第二个很好:)。
      • @Aominè:谢谢 :) 我首先想到了第一个 sn-p,最后,我在 2-3 步内收敛到了第二个 sn-p(我也学习了 Stream-API)。我决定保留第一个来比较解决方案,并通过提供替代方案来区别于另一个答案。
      【解决方案4】:

      无论您使用基于流的解决方案还是基于循环的解决方案,将内部循环提取到方法中并增加一些清晰度和结构都是有益的:

      private double computeScore(Collection<String> strings) 
      {
          return strings.stream().mapToDouble(this::computeScore).sum();
      }
      

      当然,这也可以使用循环来实现,但是......这正是重点:现在可以在外部循环中或在映射条目流的值上调用此方法。

      外部循环或流也可以被拉入方法中。在下面的示例中,我对此进行了概括:地图键的类型无关紧要。值是 List 还是 Collection 实例也不是。

      作为当前接受的答案的替代方案,此处基于流的解决方案不会填充手动创建的新地图。相反,它使用Collector

      (这与其他答案类似,但我认为提取的computeScore 方法极大地简化了嵌套流所必需的其他相当难看的 lambda)

      import java.util.Arrays;
      import java.util.Collection;
      import java.util.HashMap;
      import java.util.LinkedHashMap;
      import java.util.List;
      import java.util.Map;
      import java.util.Map.Entry;
      import java.util.stream.Collectors;
      
      public class ToStreamOrNotToStream
      {
          public static void main(String[] args)
          {
              ToStreamOrNotToStream t = new ToStreamOrNotToStream();
      
              Map<String, List<String>> inputMap =
                  new LinkedHashMap<String, List<String>>();
              inputMap.put("A", Arrays.asList("1.0", "2.0", "3.0"));
              inputMap.put("B", Arrays.asList("2.0", "3.0", "4.0"));
              inputMap.put("C", Arrays.asList("3.0", "4.0", "5.0"));
      
              System.out.println("Result A: " + t.computeA(inputMap));
              System.out.println("Result B: " + t.computeB(inputMap));
          }
      
          private <T> Map<T, Double> computeA(
              Map<T, ? extends Collection<String>> inputMap)
          {
              Map<T, Double> finalResult = new HashMap<>();
              for (Entry<T, ? extends Collection<String>> entry : inputMap.entrySet())
              {
                  double score = computeScore(entry.getValue());
                  finalResult.put(entry.getKey(), score);
              }
              return finalResult;
          }
      
          private <T> Map<T, Double> computeB(
              Map<T, ? extends Collection<String>> inputMap)
          {
              return inputMap.entrySet().stream().collect(
                  Collectors.toMap(Entry::getKey, e -> computeScore(e.getValue()))); 
          }
      
          private double computeScore(Collection<String> strings) 
          {
              return strings.stream().mapToDouble(this::computeScore).sum();
          }
      
          private double computeScore(String a)
          {
              return Double.parseDouble(a);
          }
      
      }
      

      【讨论】:

        【解决方案5】:

        我发现它有点短:

        value = startDates.entrySet().stream().mapToDouble(Entry::getValue).sum();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-01-28
          • 1970-01-01
          • 1970-01-01
          • 2018-08-02
          • 1970-01-01
          • 2020-01-26
          • 2021-09-03
          • 2020-03-17
          相关资源
          最近更新 更多