【问题标题】:Reducing list of two level maps to a single two level map using java 8 stream operators使用 java 8 流运算符将两级映射列表减少为单个两级映射
【发布时间】:2016-04-08 12:34:09
【问题描述】:

我有一个嵌套映射列表(List<Map<String, Map<String, Long>>>),目标是将列表减少为单个映射,合并如下:如果map1包含x->{y->10, z->20}map2包含x->{y->20, z->20},那么这两个应该合并到x->{y->30, z->40}

我尝试按照以下方式进行操作,效果很好。

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

public class Test {
    public static void main(String args[]) throws IOException {
        Map<String, Map<String, Long>> data1 = new HashMap<>();
        Map<String, Long> innerData1 = new HashMap<>();
        innerData1.put("a", 10L);
        innerData1.put("b", 20L);
        data1.put("x", innerData1);
        Map<String, Long> innerData2 = new HashMap<>();
        innerData2.put("b", 20L);
        innerData2.put("a", 10L);
        data1.put("x", innerData1);

        Map<String, Map<String, Long>> data2 = new HashMap<>();
        data2.put("x", innerData2);

        List<Map<String, Map<String, Long>>> mapLists = new ArrayList<>();
        mapLists.add(data1);
        mapLists.add(data2);

        Map<String, Map<String, Long>>  result  = mapLists.stream().flatMap(map -> map.entrySet().stream()).
        collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, new BinaryOperator<Map<String, Long>>() {

            @Override
            public Map<String, Long> apply(Map<String, Long> t,
                    Map<String, Long> u) {
                Map<String, Long> result = t;
                for(Entry<String, Long> entry: u.entrySet()) {
                    Long val = t.getOrDefault(entry.getKey(), 0L);
                    result.put(entry.getKey(), val + entry.getValue());
                }
                return result;
            }
        }));
    }
}

还有其他更好更有效的方法来解决这个问题吗?

如果嵌套层数大于2,怎么做更干净?假设 List 类似于 List&lt;Map&lt;String, Map&lt;String, Map&lt;String, Long&gt;&gt;&gt;&gt;,我们必须将其缩减为单个 Map&lt;String, Map&lt;String, Map&lt;String, Long&gt;&gt;&gt;,并假设与上述类似的合并功能。

【问题讨论】:

    标签: java lambda java-8 java-stream


    【解决方案1】:

    您有一个大致的想法,只是可以简化一点将两张地图合并在一起的过程。 Merging the two maps can be done easily with:

    Map<String, Integer> mx = new HashMap<>(m1);
    m2.forEach((k, v) -> mx.merge(k, v, Long::sum));
    

    此代码从m1 创建合并映射mx,然后遍历第二个映射m2 的所有条目,并在Map.merge(key, value, remappingFunction) 的帮助下将每个条目合并到mx:此方法将添加如果该键不存在映射,则使用给定值给定键,否则它将使用给定的重新映射函数重新映射该键的现有值和给定值。在我们的例子中,重映射函数应该将这两个值相加。

    代码:

    Map<String, Map<String, Long>> result  =
        mapLists.stream()
                .flatMap(m -> m.entrySet().stream())
                .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    Map.Entry::getValue, 
                    (m1, m2) -> { 
                        Map<String, Long> mx = new HashMap<>(m1);
                        m2.forEach((k, v) -> mx.merge(k, v, Long::sum));
                        return mx;
                    }
                ));
    

    如果有更多的“级别”,你可以定义一个merge 方法:

    private static <K, V> Map<K, V> merge(Map<K, V> m1, Map<K, V> m2, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Map<K, V> mx = new HashMap<>(m1);
        m2.forEach((k, v) -> mx.merge(k, v, remappingFunction));
        return mx;
    }
    

    并递归使用它。例如,要合并两个 Map&lt;String, Map&lt;String, Long&gt;&gt; m1m2,您可以使用

    merge(m1, m2, (a, b) -> merge(a, b, Long::sum));
    

    因为重映射函数是Collectors.toMap

    【讨论】:

      【解决方案2】:

      使用我的StreamEx 库:

      Map<String, Map<String, Long>> result = StreamEx.of(mapLists)
              .flatMapToEntry(m -> m)
              .toMap((m1, m2) -> EntryStream.of(m1).append(m2).toMap(Long::sum));
      

      flatMapToEntry 中间操作将映射扁平化为 EntryStream&lt;String, Map&lt;String, Long&gt;&gt;,它扩展了 Stream&lt;Map.Entry&lt;String, Map&lt;String, Long&gt;&gt;&gt;toMap 终端操作只是使用提供的合并函数从条目流创建一个映射。为了合并两张地图,我们再次使用EntryStream

      【讨论】:

      • 通常使用 StreamEx 更简单的方法 :)。
      • @Tunaki,实际上如果实现JDK-8072718 会更简单。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      • 2021-12-20
      • 1970-01-01
      • 1970-01-01
      • 2016-05-13
      相关资源
      最近更新 更多