Группировка по и отображение значений

Вопрос или проблема

Я пытаюсь сгруппировать по (отобразить), а затем преобразовать список значений в другой список.

У меня есть список DistrictDocuments:

List<DistrictDocument> docs = new ArrayList<>();

Затем я итерирую по нему и группирую по городу:

Map<String, List<DistrictDocument>> collect = docs.stream()
                .collect(Collectors.groupingBy(DistrictDocument::getCity));

У меня также есть метод, который принимает DistrictDocument и создает из него Slugable:

private Fizz createFizz(DistrictDocument doc) {
    return new Fizz().name(doc.getName()).fizz(doc.getFizz());
}

Есть ли способ вставить этот метод в мой поток выше, чтобы получить Map<String, List<Fizz>>?
Я пытался добавить второй аргумент к groupingBy, но не смог найти правильный способ и постоянно получал ошибки компиляции.

Редактирование:
Что если мой createFizz возвращает List<Fizz>? Есть ли возможность сгладить этот список в Collectors.mapping, потому что я все равно хочу получить Map<String, List<Fizz>> вместо Map<String, List<List<Fizz>>>

Вам необходимо связать коллектор Collectors.mapping() с коллектором Collectors.groupingBy():

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.groupingBy(DistrictDocument::getCity,
                 Collectors.mapping(d->createFizz(d),Collectors.toList())));

Если createFizz(d) возвращает List<Fizz, вы можете сгладить его, используя Collectors.flatMapping из Java 9:

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.groupingBy(DistrictDocument::getCity,
                 Collectors.flatMapping(d->createFizz(d).stream(),Collectors.toList())));

Если вы не можете использовать Java 9, возможно, использование Collectors.toMap поможет:

Map<String, List<Fizz>> collect =
    docs.stream()
        .collect(Collectors.toMap(DistrictDocument::getCity,
                                  d->createFizz(d),
                                  (l1,l2)->{l1.addAll(l2);return l1;}));

В случае, если вы хотите сделать двойную вложенную группировку:

Допустим, у вас есть коллекция объектов EducationData, которые содержат название школы, имя учителя и имя студента. Но вы хотите вложенную карту, которая выглядит так:

Что у вас есть

class EducationData {
     String school;
     String teacher;
     String student;

     // геттеры и сеттеры ...
}

Что вы хотите

Map<String, Map<String, List<String>>> desiredMapOfMpas ...

// которая будет выглядеть так :

"East High School" : {
     "Ms. Jackson" : ["Derek Shepherd", "Meredith Grey", ...],
     "Mr. Teresa" : ["Eleanor Shellstrop", "Jason Mendoza", ...],
     ....
}

Как к этому прийти

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

public doubleNestedGroup(List<EducationData> educations) {
     Map<String, Map<String, List<String>>> nestedMap = educations.stream()
        .collect(
             groupingBy(
                  EducationData::getSchool,
                  groupingBy(
                       EducationData::getTeacher,
                       mapping(
                            EducationData::getStudent,
                            toList()
                       )
                  )
              )
        );
}

Ответ или решение

Группировка и маппинг значений в Java Stream API

В данной статье мы рассмотрим, как выполнять группировку и преобразование значений в коллекциях при помощи Java Stream API. Мы подробно разберем, как создать карту (Map), группируя объекты по определенному полю, а затем преобразовать сгруппированные значения в другой тип.

Исходные данные

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

class DistrictDocument {
    private String city;
    private String name;
    private String fizz;

    // Геттеры и сеттеры...
}

У вас также есть метод, который принимает DistrictDocument и создает объект Fizz:

private Fizz createFizz(DistrictDocument doc) {
    return new Fizz().name(doc.getName()).fizz(doc.getFizz());
}

Группировка и преобразование

Для того чтобы получить Map<String, List<Fizz>>, вам необходимо использовать метод Collectors.groupingBy() в сочетании с Collectors.mapping(). Это выглядит следующим образом:

Map<String, List<Fizz>> collect = docs.stream()
    .collect(Collectors.groupingBy(
        DistrictDocument::getCity,
        Collectors.mapping(this::createFizz, Collectors.toList())
    ));

В данной конструкции:

  • DistrictDocument::getCity — это функция, по которой происходит группировка.
  • Collectors.mapping(this::createFizz, Collectors.toList()) — это маппинг, который преобразует каждый DistrictDocument в Fizz.

Обработка списка значений

Если ваш метод createFizz возвращает список объектов Fizz, вам потребуется использовать Collectors.flatMapping(), чтобы избежать получения вложенной структуры Map<String, List<List<Fizz>>>. Пример кода будет таким:

Map<String, List<Fizz>> collect = docs.stream()
    .collect(Collectors.groupingBy(
        DistrictDocument::getCity,
        Collectors.flatMapping(d -> createFizz(d).stream(), Collectors.toList())
    ));

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

Альтернативное решение без Java 9

Если вы используете версию Java ниже 9, можно воспользоваться методом Collectors.toMap() в сочетании с обработкой конфликтующих значений:

Map<String, List<Fizz>> collect = docs.stream()
    .collect(Collectors.toMap(
        DistrictDocument::getCity,
        d -> new ArrayList<>(Collections.singletonList(createFizz(d))),
        (l1, l2) -> { l1.addAll(l2); return l1; }
    ));

Здесь l1.addAll(l2) объединяет два списка в случае конфликтов.

Двойная группировка

Если вам необходимо создать вложенную структуру, например, Map<String, Map<String, List<String>>>, вы можете сделать это следующим образом:

class EducationData {
    private String school;
    private String teacher;
    private String student;

    // Геттеры и сеттеры...
}

public Map<String, Map<String, List<String>>> doubleNestedGroup(List<EducationData> educations) {
    return educations.stream()
        .collect(Collectors.groupingBy(
            EducationData::getSchool,
            Collectors.groupingBy(
                EducationData::getTeacher,
                Collectors.mapping(
                    EducationData::getStudent,
                    Collectors.toList()
                )
            )
        ));
}

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

Заключение

Использование Java Stream API для группировки и маппинга значений позволяет легко преобразовывать ваши данные в необходимый формат. Метод Collectors.groupingBy() в комбинации с Collectors.mapping() и Collectors.flatMapping() предоставляет мощные инструменты для работы с коллекциями. Надеюсь, эта информация была полезной и поможет вам в ваших проектах.

Оцените материал
Добавить комментарий

Капча загружается...