Получить список наименований книг по каждому автору

10. Сгруппировать названия книг по авторам

Условие задачи:
Дана коллекция объектов Book, где:

  • author — имя автора;

  • names — список названий книг.

Необходимо:

  1. получить Map<String, List<String>>, где ключ — автор, а значение — список всех названий его книг;

  2. дополнительно получить для каждого автора только уникальные названия книг.

Код:

public class Main {

    private record Book(String author, List<String> names) {
    }

    public static void main(String[] args) {
        Set<Book> books = Set.of(
                new Book("Author1", List.of("Name1")),
                new Book("Author1", List.of("Name2")),
                new Book("Author2", List.of("Name3")),
                new Book("Author2", List.of("Name4", "Name3")),
                new Book("Author3", List.of("Name5"))
        );
    }
}

Спойлеры к решению

Подсказки
💡 Сгруппируй объекты по author через Collectors.groupingBy().
💡 У каждого Book хранится список названий, поэтому вложенные списки нужно развернуть.
💡 Для этого можно использовать Collectors.flatMapping().
💡 Для обычного результата собирай названия в List, а для уникального — в Set.

Решение
  1. Список названий книг по каждому автору:
Map<String, List<String>> booksByAuthor = books.stream()
        .collect(Collectors.groupingBy(
                Book::author,
                Collectors.flatMapping(
                        book -> book.names().stream(),
                        Collectors.toList()
                )
        ));

groupingBy() группирует объекты Book по автору:

Book::author

А flatMapping() разворачивает все списки names внутри одной группы в единый поток названий:

book -> book.names().stream()

Для приведённых данных результат будет содержать:

Author1 -> [Name1, Name2]
Author2 -> [Name3, Name4, Name3]
Author3 -> [Name5]
  1. Уникальные названия книг по каждому автору:
Map<String, Set<String>> uniqueBooksByAuthor = books.stream()
        .collect(Collectors.groupingBy(
                Book::author,
                Collectors.flatMapping(
                        book -> book.names().stream(),
                        Collectors.toSet()
                )
        ));

Теперь названия собираются в Set, поэтому повторяющиеся значения исключаются:

Author1 -> [Name1, Name2]
Author2 -> [Name3, Name4]
Author3 -> [Name5]

Если используется Java 8, где Collectors.flatMapping() ещё нет, ту же задачу можно решить через toMap():

Map<String, List<String>> booksByAuthor = books.stream()
        .collect(Collectors.toMap(
                Book::author,
                book -> new ArrayList<>(book.names()),
                (left, right) -> {
                    left.addAll(right);
                    return left;
                }
        ));

Временная сложность — O(m), где m — общее количество названий книг во всех объектах Book. Дополнительная память также зависит от количества собранных названий.