#java #collections #java-8 #java-stream #groupingby
#java #Коллекции #java-8 #java-поток #группирование
Вопрос:
Учитывая
List<Student>
, где у каждого Student
есть List<Book>
, группируйте по Book
List<Student>
Рабочее решение (использует Java 9) У меня уже есть рабочий код, упомянутый ниже
public class Demo {
public static void main(String[] args) {
List<Student> studs = createStudents();
var data = studs.stream()
.flatMap(s -> s.getBooks()
.stream()
.map(b -> Pair.of(b, s))) // <------ is possible without using external class?
.collect(Collectors.groupingBy(
p -> p.getBook(),
Collectors.mapping(p -> p.getStud().getFirstName(), Collectors.toList())));
System.out.println("All books: " data);
}
private static List<Student> createStudents() {
Book javaCompleteReference = new Book("Java Complete Reference");
Book ocjp = new Book("Java OCJP");
Book sql = new Book("SQL");
Book spring = new Book("SPRING in Action");
return List.of(
new Student(1, "A", List.of(javaCompleteReference, spring)),
new Student(2, "B", List.of(spring, sql, ocjp)),
new Student(3, "C", List.of(javaCompleteReference, sql))
);
}
}
Ищете:
- Я использовал flatMap и пару промежуточных классов. Возможно ли достичь конечного результата без использования промежуточного преобразования с использованием класса Pair
База кода:
public class Book {
private String title;
private String id;
private Integer pages;
public Book(String title, String id, Integer pages) {
this.title = title;
this.id = id;
this.pages = pages;
}
public Book(String title) { this.title = title; }
public String getTitle() { return title; }
public String getId() { return id; }
public Integer getPages() { return pages; }
@Override
public String toString() { return title; }
}
class Pair {
Book book;
Student stud;
private Pair(Book book, Student stud) {
this.book = book;
this.stud = stud;
}
static Pair of(Book book, Student stud) { return new Pair(book, stud); }
public Book getBook() { return book; }
public Student getStud() { return stud; }
}
public class Student {
private Integer id;
private String firstName;
private List<Book> books;
public Student(Integer id, String firstName, List<Book> books) {
this.id = id;
this.firstName = firstName;
this.books = books;
}
public Integer getId() { return id; }
public String getFirstName() { return firstName; }
public List<Book> getBooks() { return books; }
}
Вывод: All books: {Java Complete Reference=[A, C], SQL=[B, C], SPRING in Action=[A, B], Java OCJP=[B]}
Комментарии:
1. Пожалуйста, прочитайте: могу ли я задать только один вопрос за сообщение? — » является ли попытка решения элегантной » — на основе мнений. — » есть лучший способ добиться результата». — Какова ваша метрика для улучшения ? Этот вопрос неясен.
2. ОК. Сократил формулировку проблемы до одного вопроса
3. Я не думаю, что это возможно, поскольку мы можем читать каждый
Student
только один раз, поэтому нам нужно создать некоторый временный ресурс (Pair
экземпляры) для повторного чтения. Почему вы думаете, что решение безmap
шага » лучше «? Или, скорее: какую актуальную проблему вы пытаетесь решить?4. Я думаю, что два вложенных
forEach
более удобочитаемы. тем не менее, вы тоже можете сделать это.studs.stream().collect(HashMap::new, (m, s) -> s.getBooks().forEach(b -> m.merge(b, new ArrayList<>(Collections.singletonList(s.getFirstName())), (l1, l2) -> { l1.addAll(l2);return l1; })), HashMap::putAll);
5. обсуждения добавили ценность моим знаниям, однако я думаю, что моя существующая реализация более понятна, чем другие умные предложения.
Ответ №1:
Это невозможно с помощью Stream API, если вам необходимо сохранить как информацию об имени учащегося, так и о каждой книге в контексте, чтобы сформировать выходную карту. Плоское отображение в один элемент (пару) — это единственный путь.
Доказательством и альтернативным (я думаю, не лучшим) решением является использование комбинации Collectors.flatMapping
и Collectors.toMap
, где вы определяете все keyMapper
, valueMapper
и mergeFunction
. Вы можете ясно видеть, как в Collectors.grouping
, вам нужна комбинация имен всех студентов и каждой книги для каждого из них.
var data = studs.stream().collect(Collectors.flatMapping(
student -> student.getBooks()
.stream()
.map(book -> new AbstractMap.SimpleEntry<>(
student.getFirstName(), book)),
Collectors.toMap(
AbstractMap.SimpleEntry::getValue,
entry -> new ArrayList<>(List.of(entry.getKey())),
(leftList, rightList) -> { leftList.addAll(rightList); return leftList; })));
Ответ №2:
возможно ли без использования внешнего класса?
Да, это возможно даже в более ранних версиях java. Просто используйте AbstractMap.SimpleEntry<K, V>
вместо:
.map(b -> new AbstractMap.SimpleEntry<>(b, s)))
Затем вы можете получить доступ к значениям, вызвав getKey()
и getValue()
.
Комментарии:
1. Чем это отличается от использования a
Pair
? Можем ли мы на самом деле создать aMap
с коллектором, повторно используяSimpleEntry
s?2. @Turing85 «Чем это отличается … ?». Ну, по крайней мере, вам не нужно определять другой класс. Что касается повторного использования SimpleEntry, не уверен. Возможно, мы можем.