Подтвердить что ты не робот

Объединение потоков карт с использованием выражения Java 8 Lambda

Я новичок в лямбда-выражении и пытался использовать его в своем назначении.

Проблема:

У меня есть две карты m1 и m2 типа Map<Integer, String>, которые должны быть объединены в одну карту 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;
                                }));

Но это решение ожидает, что source List будет Map<Integer, List<String>>, поскольку функция слияния в toMap ожидает, что операнды и результат будут одного типа.

Я не хочу менять исходную коллекцию. Просьба представить ваши данные о достижении этого, используя выражение лямбда.

4b9b3361

Ответ 1

Я не могу проверить это прямо сейчас, но я думаю, что все, что вам нужно, чтобы изменить отображение значения из 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;
                                    }));

EDIT: Идея была правильной. Синтаксиса не было. Текущий синтаксис работает, хотя и немного уродлив. Должен быть более короткий способ написать его.

Вы также можете заменить 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())));

Ответ 2

Это задача для groupingBy сборщика:

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

Ответ 3

Решение Misha является лучшим, если вы хотите чистое решение Java-8. Если вы не возражаете против использования сторонних библиотек, это будет немного короче, используя StreamEx.

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

Внутри это то же самое, что и в решении Миши, просто синтаксический сахар.

Ответ 4

Это кажется отличной возможностью использовать 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);