Filtering nested collections with Streams – Functional style programming – extending API
185. Filtering nested collections with Streams
This is a classical problem in interviews that usually starts from a model as follows (we assume that the collection is a List):
public class Author {
private final String name;
private final List<Book> books;
…
}
public class Book {
private final String title;
private final LocalDate published;
…
}
Having a List<Author> denoted as authors, write a stream pipeline that returns the List<Book> published in 2002. You already should recognize this as a typical problem for flatMap(), so without further details, we can write this:
List<Book> book2002fm = authors.stream()
.flatMap(author -> author.getBooks().stream())
.filter(book -> book.getPublished().getYear() == 2002)
.collect(Collectors.toList());
From Problem 178, we know that where flatMap() is useful, we should consider the JDK 16’s mapMulti() as well. Before checking the following snippet of code, challenge yourself to re-write the previous code via mapMulti():
List<Book> book2002mm = authors.stream()
.<Book>mapMulti((author, consumer) -> {
for (Book book : author.getBooks()) {
if (book.getPublished().getYear() == 2002) {
consumer.accept(book);
}
}
})
.collect(Collectors.toList());
Ok, that’s crystal clear! How about finding the List<Author> having books published in 2002? Of course, mapMulti() can help us again. All we have to do is to loop the books and, when we find a book published in 2002, we simply pass the author to the consumer instead of the book. Moreover, after passing the author to the consumer, we can break the loop for the current author and take the next one:
List<Author> author2002mm = authors.stream()
.<Author>mapMulti((author, consumer) -> {
for (Book book : author.getBooks()) {
if (book.getPublished().getYear() == 2002) {
consumer.accept(author);
break;
}
}
})
.collect(Collectors.toList());
Another approach can rely on anyMatch() and a predicate that produces a stream of books published in 2002 as follows:
List<Author> authors2002am = authors.stream()
.filter(
author -> author.getBooks()
.stream()
.anyMatch(book -> book.getPublished()
.getYear() == 2002)
)
.collect(Collectors.toList());
Typically, we don’t want to alter the given list, but if that is not an issue (or, is exactly what we want), then we can rely on removeIf() to accomplish the same result directly on the List<Author>:
authors.removeIf(author -> author.getBooks().stream()
.noneMatch(book -> book.getPublished().getYear() == 2002));
Done! Now, you should have no issues caused by such problems in your interviews.
Leave a Reply