Как объединить поток>> в одну карту>? - PullRequest
3 голосов
/ 08 июня 2019

Я пытаюсь объединить объект Stream<Map<String, Map<String, String>>> в одну карту с ключами во всех Streams.

Например,

final Map<String, someOtherObjectToProcess> someObject;

final List<Map<String, Map<String, String>>> list = someObject.entrySet()
            .stream()
            .flatMap(this::getInfoStream)
            .collect(Collectors.toList());

Подпись для getInfoStream

public Stream<Map<String, Map<String, String>>> getInfoStream(Map.Entry<String, someOtherObjectToProcess> entry)

если я использую (Collectors.toList()), я могу получить список этих объектов карты.

Пример вывода, если я использую приведенный выше код:

[{
    "name" : {
        "property":"value"
    }
},

{
    "name2" : {
        "property":"value"
    }
}]

Но я хочу собрать в Карту со структурой

{
    "name" : {
        "property":"value"
    },
    "name2" : {
        "property":"value"
    }
}

При условии, что ключи будут уникальными.

Как я могу сделать это с Collectors.toMap () или любым другим альтернативным способом?

Ответы [ 4 ]

3 голосов
/ 08 июня 2019

Когда у вас есть

Stream<Map<String, Map<String, String>>> stream = ...

(который я предполагаю, является результатом .flatMap(this::getInfoStream)), вы можете позвонить

.flatMap(map -> map.entrySet().stream())

для создания потока записей со всех карт, которые будут выдавать Stream<Map.Entry<String, Map<String, String>>>.

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

.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

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

.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (vOld, vNew) -> ...));
//                                                                                ^^^

где vOld содержит значение, хранящееся в текущий момент в карте результатов под тем же ключом, а vNew содержит новое значение (из текущей итерации потока).
Например, если вы хотите игнорировать новое значение, вы можете просто вернуть старое / текущее значение (vOld, vNew) -> vOld

Короче говоря (при условии уникальных ключей):

Map<String, Map<String, String>> combinedMap = 
        /*your Stream<Map<String, Map<String, String>>>*/
        .flatMap(map -> map.entrySet().stream())
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1 голос
/ 08 июня 2019

TL; DR:

var merged = Stream.of(map1, map2, ..., mapN).reduce(new HashMap<>(), (a, b) -> {
    a.putAll(b);
    return a;
});

Вы можете использовать reduce для объединения потока Map<String, Map<String, String>> элементов в один:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        alternative1();
        alternative2();
    }

    // Use reduce without an initial identity value
    public static void alternative1() {
        Map<String, Map<String, String>> m1 = new HashMap<>();
        m1.put("name", Map.of("property", "value"));

        Map<String, Map<String, String>> m2 = new HashMap<>();
        m2.put("name2", Map.of("property", "value"));

        Stream<Map<String, Map<String, String>>> mapStream = Stream.of(m1, m2);

        Map<String, Map<String, String>> m3 = mapStream.reduce((a, b) -> {
            Map<String, Map<String, String>> temp = new HashMap<>();
            temp.putAll(a);
            temp.putAll(b);
            return temp;
        }).orElseThrow();

        System.out.println(m3);
    }

    // Use reduce with an initial empty map as the identity value
    public static void alternative2() {
        Map<String, Map<String, String>> m1 = new HashMap<>();
        m1.put("name", Map.of("property", "value"));

        Map<String, Map<String, String>> m2 = new HashMap<>();
        m2.put("name2", Map.of("property", "value"));

        Stream<Map<String, Map<String, String>>> mapStream = Stream.of(m1, m2);

        Map<String, Map<String, String>> m3 = mapStream.reduce(new HashMap<>(), (a, b) -> {
            a.putAll(b);
            return a;
        });

        System.out.println(m3);
    }
}

Выход:

{name={property=value}, name2={property=value}}
{name={property=value}, name2={property=value}}

Но имейте в виду, что эти решения предполагают, что ключи (name и name2) уникальны, в противном случае дублированные ключи заставят записи карты перезаписывать друг друга.


Та же логика с более современным синтаксисом:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {
        alternative1();
        alternative2();
    }

    // Use reduce without an initial identity value
    public static void alternative1() {
        var m1 = Map.of("name", Map.of("property", "value"));
        var m2 = Map.of("name2", Map.of("property", "value"));
        var m3 = Stream.of(m1, m2).reduce((a, b) -> {
            var temp = new HashMap<String, Map<String, String>>();
            temp.putAll(a);
            temp.putAll(b);
            return temp;
        }).orElseThrow();

        System.out.println(m3);
    }

    // Use reduce with an initial empty map as the identity value
    public static void alternative2() {
        var m1 = Map.of("name", Map.of("property", "value"));
        var m2 = Map.of("name2", Map.of("property", "value"));
        var m3 = Stream.of(m1, m2).reduce(new HashMap<>(), (a, b) -> {
            a.putAll(b);
            return a;
        });

        System.out.println(m3);
    }
}
1 голос
/ 08 июня 2019

Самый читаемый способ, на мой взгляд, состоит в том, чтобы сопоставить все с Map.Entry, а затем собрать все обратно на Map, используя Collectors::toMap

import static java.util.stream.Collectors.toMap;

// ...

someObject.entrySet()
          .stream()
          .flatMap(this::getInfoStream)
          .flatMap(map -> map.entrySet().stream())
          .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (one, two) -> one));

(one, two) -> one - это функция слияния,в основном, если у вас есть дубликаты, вы просто выбираете первый случай, который появляется

1 голос
/ 08 июня 2019

Другим способом решения этой проблемы было бы не использование collector(toList()), а другой перегруженный метод .collect() с Поставщиком, Аккумулятором и Объединителем:

Stream<Map<String, Map<String, String>>> stream = ...
Map<String, Map<String, String>> result = stream
       .collect(HashMap::new, HashMap::putAll, HashMap::putAll);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...