Java-Stream — разделение, группировка и сопоставление данных из строки с использованием одного потока

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

String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";

И я хочу преобразовать его в карту типа Map<String,List<String>> (как показано ниже), выполнив следующие шаги:

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

Пример полученной карты:

{ 
  027=[wuchang, hongshan, caidan],
  020=[tianhe],
  010=[fengtai, chaoyang, haidain],
  021=[changnin, xuhui]
}

Я использовал традиционный способ достижения этого:

private Map<String, List<String>> parseParametersByIterate(String sensors) {
    List<String[]> dataList = Arrays.stream(sensors.split(","))
        .map(s -> s.split("\\$\\$"))
        .collect(Collectors.toList());
    
    Map<String, List<String>> resultMap = new HashMap<>();
    for (String[] d : dataList) {
        List<String> list = resultMap.get(d[0]);
        if (list == null) {
            list = new ArrayList<>();
            list.add(d[1]);
            resultMap.put(d[0], list);
        } else {
            list.add(d[1]);
        }
    }
    return resultMap;
}

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

То, что я пробовал до сих пор, ниже

Map<String, List<String>> result =  Arrays.stream(data.split(","))
    .collect(Collectors.groupingBy(s -> s.split("\\$\\$")[0]));

Но результат не соответствует тому, который я хочу иметь. Как я могу создать карту, структурированную, как описано выше?

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

Ответы 2

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

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

Collectors.groupingBy(s -> s.split("\\$\\$")[0],
    Collectors.mapping(s -> s.split("\\$\\$")[1],
        Collectors.toList()
))

Вместо того, чтобы разбивать дважды, вы можете сначала разбить, а потом сгруппировать:

Arrays.stream(data.split(","))
    .map(s -> s.split("\\$\\$"))
    .collect(Collectors.groupingBy(s -> s[0],
        Collectors.mapping(s -> s[1],Collectors.toList())
    ));

Что теперь выводит:

{027=[wuchang, hongshan, caidan], 020=[tianhe], 021=[changnin, xuhui], 010=[fengtai, chaoyang, haidain]}

Ага, не хочу раздваиваться, спасибо за ответ, приму через 15 минут

flyingfox 15.11.2022 10:54

Вы можете еще больше повысить производительность, используя .map(s -> s.split("\\$\\$", 2)), так как это сообщит операции split, что ей не нужно искать другой $$ после того, как он его нашел.

Holger 15.11.2022 11:25

Кстати, split(",") и split("\\$\\$") можно комбинировать за один шаг, не создавая промежуточных массивов — см..

Alexander Ivanchenko 05.12.2022 19:58

Вы можете извлечь необходимую информацию из строки, не выделяя промежуточные массивы и выполняя итерацию по строке только один раз, а также используя механизм регулярных выражений только один раз вместо того, чтобы выполнять несколько вызовов String.split() и разбивать сначала запятую ,, а затем $$. Мы можем получить все необходимые данные за один раз.

Поскольку вы уже используете регулярные выражения (поскольку интерпретация "\\s\\s" требует использования механизма регулярных выражений), было бы разумно использовать их на полную мощность.

Matcher.results()

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

public static final Pattern DATA = // use the proper name to describe a piece of information (like "027$$hongshan") that the pattern captures
    Pattern.compile("(\\d+)\\$\\$(\\w+)");

Используя этот шаблон, мы можем создать экземпляр Matcher и применить метод Java 9 Matcher.result(), который создаст поток MatchResults.

MatchResult — это объект, инкапсулирующий информацию о захваченной последовательности символов. Мы можем получить доступ к группам, используя метод MatchResult.group().

private static Map<String, List<String>> parseParametersByIterate(String sensors) {
    
    return DATA.matcher(sensors).results() // Stream<MatchResult>
        .collect(Collectors.groupingBy(
            matchResult -> matchResult.group(1),     // extracting "027" from "027$$hongshan"
            Collectors.mapping(
                matchResult -> matchResult.group(2), // extracting "hongshan" from "027$$hongshan"
                Collectors.toList())
        ));
}

main()

public static void main(String[] args) {
    String data = "010$$fengtai,010$$chaoyang,010$$haidain,027$$wuchang,027$$hongshan,027$$caidan,021$$changnin,021$$xuhui,020$$tianhe";
    
    parseParametersByIterate(data)
        .forEach((k, v) -> System.out.println(k + " -> " + v));
}

Вывод:

027 -> [wuchang, hongshan, caidan]
020 -> [tianhe]
021 -> [changnin, xuhui]
010 -> [fengtai, chaoyang, haidain]

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