使用 Java 8 Stream API 合并两个 Map<String, Integer>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23038673/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 20:13:49  来源:igfitidea点击:

Merging two Map<String, Integer> with Java 8 Stream API

javamergejava-8java-stream

提问by user3528157

I have two (or more) Map<String, Integer>objects. I'd like to merge them with Java 8 Stream API in a way that values for common keys should be the maximum of the values.

我有两个(或更多)Map<String, Integer>对象。我想将它们与 Java 8 Stream API 合并,使公共键的值应该是值的最大值。

@Test
public void test14() throws Exception {
    Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3);
    Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4);
    List<Map<String, Integer>> list = newArrayList(m1, m2);

    Map<String, Integer> mx = list.stream()... // TODO

    Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4);
    assertEquals(expected, mx);
}

How can I make this test method green?

我怎样才能使这个测试方法变绿?

I've played with collectand Collectorsfor a while without any success.

我已经打了collect,并Collectors有一阵子没有任何成功。

(ImmutableMapand newArrayListare from Google Guava.)

ImmutableMapnewArrayList来自谷歌番石榴)。

采纳答案by srborlongan

@Test
public void test14() throws Exception {
    Map<String, Integer> m1 = ImmutableMap.of("a", 2, "b", 3);
    Map<String, Integer> m2 = ImmutableMap.of("a", 3, "c", 4);

    Map<String, Integer> mx = Stream.of(m1, m2)
        .map(Map::entrySet)          // converts each map into an entry set
        .flatMap(Collection::stream) // converts each set into an entry stream, then
                                     // "concatenates" it in place of the original set
        .collect(
            Collectors.toMap(        // collects into a map
                Map.Entry::getKey,   // where each entry is based
                Map.Entry::getValue, // on the entries in the stream
                Integer::max         // such that if a value already exist for
                                     // a given key, the max of the old
                                     // and new value is taken
            )
        )
    ;

    /* Use the following if you want to create the map with parallel streams
    Map<String, Integer> mx = Stream.of(m1, m2)
        .parallel()
        .map(Map::entrySet)          // converts each map into an entry set
        .flatMap(Collection::stream) // converts each set into an entry stream, then
                                     // "concatenates" it in place of the original set
        .collect(
            Collectors.toConcurrentMap(        // collects into a map
                Map.Entry::getKey,   // where each entry is based
                Map.Entry::getValue, // on the entries in the stream
                Integer::max         // such that if a value already exist for
                                     // a given key, the max of the old
                                     // and new value is taken
            )
        )
    ;
    */

    Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 3, "c", 4);
    assertEquals(expected, mx);
}

回答by Stuart Marks

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

回答by Sean Van Gorder

mx = list.stream().collect(HashMap::new,
        (a, b) -> b.forEach((k, v) -> a.merge(k, v, Integer::max)),
        Map::putAll);

This covers the general case for any size list and should work with any types, just swap out the Integer::maxand/or HashMap::newas desired.

这涵盖了任何大小列表的一般情况,应该适用于任何类型,只需根据需要交换Integer::max和/或HashMap::new

If you don't care which value comes out in a merge, there's a much cleaner solution:

如果您不在乎合并中出现哪个值,则有一个更简洁的解决方案:

mx = list.stream().collect(HashMap::new, Map::putAll, Map::putAll);

And as generic methods:

作为通用方法:

public static <K, V> Map<K, V> mergeMaps(Stream<? extends Map<K, V>> stream) {
    return stream.collect(HashMap::new, Map::putAll, Map::putAll);
}

public static <K, V, M extends Map<K, V>> M mergeMaps(Stream<? extends Map<K, V>> stream,
        BinaryOperator<V> mergeFunction, Supplier<M> mapSupplier) {
    return stream.collect(mapSupplier,
            (a, b) -> b.forEach((k, v) -> a.merge(k, v, mergeFunction)),
            Map::putAll);
}

回答by Alexis C.

I added my contribution to the proton pack librarywhich contains utility methods for the Stream API. Here's how you could achieve what you want:

我将我的贡献添加到包含用于 Stream API 的实用程序方法的质子包库中。以下是您如何实现您想要的:

Map<String, Integer> mx = MapStream.ofMaps(m1, m2).mergeKeys(Integer::max).collect();

Basically mergeKeyswill collect the key-value pairs in a new map (providing a merge function is optional, you'll end up with a Map<String, List<Integer>>otherwise) and recall stream()on the entrySet()to get a new MapStream. Then use collect()to get the resulting map.

基本上mergeKeys将收集在一个新的地图的键值对(提供合并功能是可选的,你会得到一个最终Map<String, List<Integer>>以其他方式),并召回stream()entrySet()得到一个新的MapStream。然后用于collect()获取生成的地图。

回答by Graeme Moss

Using StreamExyou can do:

使用StreamEx,您可以:

StreamEx.of(m1, m2)
    .flatMapToEntry(x -> x)
    .grouping(IntCollector.max())

回答by giannis christofakis

I've created a visual representation of what @srborlongan did, for anyone who might be interested.

我为任何可能感兴趣的人创建了@srborlongan 所做的事情的可视化表示。

Diagram displaying maps convert to stream of entries

显示地图的图表转换为条目流