【问题标题】:How to combine Map values from parent Map with java 8 stream如何将父地图中的地图值与java 8流结合起来
【发布时间】:2020-11-30 09:54:00
【问题描述】:

我在地图内有一张地图,如下所示:

Map<String, Map<Integer, BigDecimal>> mapInMap; //--> with values like: 
/*
"a": (1: BigDecimal.ONE),
"b": (2: BigDecimal.TEN),
"c": (1: BigDecimal.ZERO)
*/

我想通过期望以下结果来组合内部地图:

Map<Integer, BigDecimal> innerMapCombined; //--> with values:
/*
1: BigDecimal.ZERO,
2: BigDecimal.TEN
*/

这是我预定义组合地图并使用 forEach 的解决方案:

Map<Integer, BigDecimal> combined = new HashMap<>();
mapInMap.forEach((str, innerMap) -> {
  innerMap.forEach(combined::putIfAbsent);
});

但这会忽略(1: BigDecimal.ZERO)

您能否提供一个带有 java 8 流的单行解决方案?

【问题讨论】:

    标签: java java-stream multimap


    【解决方案1】:

    您的问题是,一旦您初始化地图,并在内部地图上添加 重复键,您将重写这些键,因为 那些地图不接受重复的键。因此,您需要先更改:

    Map<String, Map<Integer, BigDecimal>> mapInMap;
    

    到一个允许重复键Map,例如来自Google Guava的Multimap

    Map<String, Multimap<Integer, BigDecimal>> mapInMap = new HashMap<>();
    

    内部地图的创建位置如下:

     Multimap<Integer, BigDecimal> x1 = ArrayListMultimap.create();
     x1.put(1, BigDecimal.ONE);
     mapInMap.put("a", x1);
    

    只有现在您可以尝试使用 Java 8 Streams API 解决您的问题。例如:

    Map<Integer, BigDecimal> map = multiMap.values()
                                           .stream()
                                           .flatMap(map -> map.entries().stream())
                                           .collect(Collectors.toMap(Map.Entry::getKey,
                                                                     Map.Entry::getValue, 
                                                                     (v1, v2) -> v2));
    

    使用toMap 方法的mergeFunction 参数解决重复键冲突。我们明确表示在重复的情况下取第二个值(v1, v2) -&gt; v2

    【讨论】:

      【解决方案2】:

      问题:

      要解决您当前的解决方案不起作用的原因是因为 Map#putIfAbsent 方法仅添加而不替换映射中已经存在的值。


      使用 for-each 的解决方案:

      Map#put 是一种可行的方法,但它的局限性在于您无法决定是否要始终保留此类键的第一个值、计算新值或始终使用最后一个值。出于这个原因,我建议使用Map#computeIfPresentMap#putIfAbsent 的组合或更好的方法,即Map#merge(K, V, BiFunction)BiFunction remappingFunction

      remappingFunction - 重新计算值(如果存在)的函数

      Map<Integer, BigDecimal> resultMap = new HashMap<>();
      for (Map<Integer, BigDecimal> map: mapInMap.values()) {
          for (Map.Entry<Integer, BigDecimal> entry: map.entrySet()) {
               resultMap.merge(entry.getKey(), entry.getValue(), (l, r) -> r);
          }
      }
      

      使用 Stream API 的解决方案:

      要在 Stream-alike 解决方案中重写它,方法是相同的。唯一的区别是 Stream API 的声明式语法,但是思路是一样的。

      只需对结构进行平面映射,然后使用BinaryOperator mergeFunction 将重复的键合并到带有Collector.toMap(Function, Function, BinaryOperator 的映射。

      mergeFunction - 一个合并函数,用于解决与同一键关联的值之间的冲突,提供给 Map.merge(Object, Object, BiFunction)

      Map<Integer, BigDecimal> resultMap = mapInMap.values().stream()
          .flatMap(entries -> entries.entrySet().stream())
          .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (l, r) -> r));
      

      注意:@dreamcrash 在速度方面的出色 Stream API answer 也值得称赞。


      结果:

      {1=1, 2=10} 是您打印出此类地图时的结果(请注意,BigDecimal 打印为数字)。此输出与您的预期输出匹配。

      1=BigDecimal.ZERO
      2=BigDecimal.TEN
      

      注意Map#merge(K, V, BiFunction)Collector.toMap(Function, Function, BinaryOperator 之间的相似之处,它们使用非常相似的方法来获得相同的结果。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-27
        • 1970-01-01
        • 1970-01-01
        • 2020-06-29
        • 1970-01-01
        • 1970-01-01
        • 2022-07-20
        相关资源
        最近更新 更多