Как преобразовать List<String> в Map<String,List<String>> на основе разделителя

У меня есть список строк, например:

List<String> locations = Arrays.asList("US:5423","US:6321","CA:1326","AU:5631");

И я хочу преобразовать в Map<String, List<String>> вот так:

AU = [5631]
CA = [1326]
US = [5423, 6321]

Я пробовал этот код, и он работает, но в этом случае мне нужно создать новый класс GeoLocation.java.

List<String> locations=Arrays.asList("US:5423", "US:6321", "CA:1326", "AU:5631");
Map<String, List<String>> locationMap = locations
        .stream()
        .map(s -> new GeoLocation(s.split(":")[0], s.split(":")[1]))
        .collect(
                Collectors.groupingBy(GeoLocation::getCountry,
                Collectors.mapping(GeoLocation::getLocation, Collectors.toList()))
        );

locationMap.forEach((key, value) -> System.out.println(key + " = " + value));

GeoLocation.java

private class GeoLocation {
    private String country;
    private String location;

    public GeoLocation(String country, String location) {
        this.country = country;
        this.location = location;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

Но я хочу знать, есть ли способ преобразовать List<String> в Map<String, List<String>> без введения нового класса.

Отсутствие кортежей в Java снова поражает :(

Alexander 01.06.2019 00:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
19
1
14 322
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

Вы можете сделать это так:

Map<String, List<String>> locationMap = locations.stream()
        .map(s -> s.split(":"))
        .collect(Collectors.groupingBy(a -> a[0],
                Collectors.mapping(a -> a[1], Collectors.toList())));

Гораздо лучшим подходом было бы,

private static final Pattern DELIMITER = Pattern.compile(":");

Map<String, List<String>> locationMap = locations.stream()
    .map(s -> DELIMITER.splitAsStream(s).toArray(String[]::new))
        .collect(Collectors.groupingBy(a -> a[0], 
            Collectors.mapping(a -> a[1], Collectors.toList())));

Обновлять

Согласно следующему комментарию, это можно упростить до:

Map<String, List<String>> locationMap = locations.stream().map(DELIMITER::split)
    .collect(Collectors.groupingBy(a -> a[0], 
        Collectors.mapping(a -> a[1], Collectors.toList())));

Я не вижу, чем второй подход лучше. Не могли бы вы уточнить?

Michael A. Schaffrath 31.05.2019 08:39

Я не уверен, что это правильно. Если вы посмотрите на исходный код String.split, вы увидите, что для односимвольных строк сделано множество оптимизаций.

Michael A. Schaffrath 31.05.2019 08:51

Последний превосходит первый, поскольку использует предварительно скомпилированный шаблон.

Ravindra Ranwala 31.05.2019 08:58
DELIMITER.splitAsStream(s).toArray(String[]::new) почему бы просто не использовать DELIMITER.split(s)?
Lino 31.05.2019 09:09

Странный результат обнаружен при попытке с 10 миллионами местоположений, предварительно скомпилированный шаблон занимает около 0,830 секунды, а разделение строки занимает около 0,58 секунды, гораздо лучший подход - не лучший подход, из которого вы упомянули. Поэтому я использую s -> s.split(":")

Vinit Solanki 31.05.2019 14:58

Попробуй это

Map<String, List<String>> locationMap = locations.stream()
            .map(s ->  new AbstractMap.SimpleEntry<String,String>(s.split(":")[0], s.split(":")[1]))
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                     Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Здесь можно оптимизировать использование split дважды.

Naman 31.05.2019 08:20

@Наман, верно! только что скопировал ответ OP....map(s->s.split(":")) .map(s -> new AbstractMap.SimpleEntry<String,String>(s[0],s[1]))... хотя @Ravindra Ranwala лучше

Hadi J 31.05.2019 08:23

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

Map<String, List<String>> locationMap = locations
            .stream()
            .map(s -> s.split(":"))
            .collect( Collectors.groupingBy( s -> s[0], Collectors.mapping( s-> s[1], Collectors.toList() ) ) );

Я заметил это после публикации

n1t4chi 31.05.2019 08:23

А как же ПОЖО. Выглядит несложно по сравнению со стримами.

public static Map<String, Set<String>> groupByCountry(List<String> locations) {
    Map<String, Set<String>> map = new HashMap<>();

    locations.forEach(location -> {
        String[] parts = location.split(":");
        map.compute(parts[0], (country, codes) -> {
            codes = codes == null ? new HashSet<>() : codes;
            codes.add(parts[1]);
            return codes;
        });
    });

    return map;
}

Похоже, ваша карта местоположения должна быть отсортирована на основе ключей, вы можете попробовать следующее

List<String> locations = Arrays.asList("US:5423", "US:6321", "CA:1326", "AU:5631");

    Map<String, List<String>> locationMap = locations.stream().map(str -> str.split(":"))
            .collect(() -> new TreeMap<String, List<String>>(), (map, parts) -> {
                if (map.get(parts[0]) == null) {
                    List<String> list = new ArrayList<>();
                    list.add(parts[1]);
                    map.put(parts[0], list);
                } else {
                    map.get(parts[0]).add(parts[1]);
                }
            }, (map1, map2) -> {
                map1.putAll(map2);
            });

    System.out.println(locationMap); // this outputs {AU=[5631], CA=[1326], US=[5423, 6321]}

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

Vinit Solanki 31.05.2019 12:53

Другие вопросы по теме