使用Java 8 Lambda表达式合并Map流

15

我有两个类型为Map<Integer, String>的地图m1m2,它们需要合并成一个新的Map<Integer, List<String>>,其中相同键的值将被收集到一个列表中,并放入新的地图中。

基于我的探索,解决方案如下:

Map<Integer, List<String>> collated =
        Stream.concat(m1.entrySet().stream(), m2.entrySet().stream()).collect(
                Collectors.toMap(Entry::getKey,
                        Entry::getValue, (a, b) -> {
                            List<String> merged = new ArrayList<String>(a);
                            merged.addAll(b);
                            return merged;
                        }));

然而,这个解决方案期望源ListMap<Integer, List<String>>,因为toMap中的合并函数期望操作数和结果具有相同的类型。

我不想改变源集合。请提供使用lambda表达式实现此目标的输入。

5个回答

22

这是一个使用groupingBy收集器的工作:

Stream.of(m1,m2)
        .flatMap(m->m.entrySet().stream())
        .collect(groupingBy(
                Map.Entry::getKey,
                mapping(Map.Entry::getValue, toList())
        ));

11

我现在无法进行测试,但我认为你需要做的就是将值从Entry::getValue的映射更改为包含该值的列表:

Map<Integer, List<String>> collated =
        Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
                .collect(Collectors.toMap(Entry::getKey,
                        e -> {
                            List<String> v = new ArrayList<String>();
                            v.add(e.getValue());
                            return v;
                        },
                        (a, b) -> {
                            List<String> merged = new ArrayList<String>(a);
                            merged.addAll(b);
                            return merged;
                        }));

编辑:想法是正确的,但语法错误了。当前的语法是可行的,虽然有点丑陋。一定有更短的方法来编写它。

你还可以将e -> {..}替换为e -> new ArrayList<String>(Arrays.asList(new String[]{e.getValue()}))

或者使用e -> Stream.of(e.getValue()).collect(Collectors.toList())

或者您可以使用groupingBy来完成:

Map<Integer, List<String>> collated =
        Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
                .collect(Collectors.groupingBy(Map.Entry::getKey,
                        Collectors.mapping(Map.Entry::getValue,
                                Collectors.toList())));

3

如果您想要纯Java-8解决方案,Misha的解决方案是最好的。如果您不介意使用第三方库,则可以使用我的StreamEx稍微简短一些。

Map<Integer, List<String>> map = StreamEx.of(m1, m2)
        .flatMapToEntry(Function.identity())
        .grouping();

在内部,它与Misha的解决方案相同,只是语法糖。


3

这似乎是使用Guava的Multimap的绝佳机会。

ListMultimap<Integer, String> collated = ArrayListMultimap.create();
collated.putAll(Multimaps.forMap(m1));
collated.putAll(Multimaps.forMap(m2));

如果你确实需要一个 Map<Integer, List<String>>

Map<Integer, List<String>> mapCollated = Multimaps.asMap(collated);

0

您可以使用带有三个参数的Collectors.toMap方法以增加清晰度。

Map<Integer, String> m1 = Map.of(1, "A", 2, "B");
Map<Integer, String> m2 = Map.of(1, "C", 2, "D");

// two maps into one
Map<Integer, List<String>> m3 = Stream
        .of(m1.entrySet(), m2.entrySet())
        .flatMap(Collection::stream)
        .collect(Collectors.toMap(
                // key - Integer
                e -> e.getKey(),
                // value - List<String>
                e -> List.of(e.getValue()),
                // mergeFunction - two lists into one
                (list1, list2) -> {
                    List<String> list = new ArrayList<>();
                    list.addAll(list1);
                    list.addAll(list2);
                    return list;
                }));

// output
System.out.println(m3); // {1=[A, C], 2=[B, D]}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接