Reputation: 61
There is a condition that I need values in the following Set
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(List::stream)
.map(StudentDetail::getName())
.filter(s -> s.contains("A"))
.collect(Collectors.toSet());
I want to filter student names containing "A" if List<StudentDetail> details
in StudentResponse
contains more than 5 elements. If not, I want to take all names in StudentDetail
. Is there any way to handle this condition?
Upvotes: 1
Views: 150
Reputation: 298579
You can use
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> l.stream()
.map(StudentDetail::getName)
.filter(s -> l.size() <= 5 || s.contains("A")))
.collect(Collectors.toSet());
but it has the disadvantage of re-checking a list condition for every element, despite it shouldn't change during the entire traversal. A better solution is not to perform a filter
operation when it is not necessary, like
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> l.size() <= 5?
l.stream().map(StudentDetail::getName):
l.stream().map(StudentDetail::getName).filter(s -> s.contains("A")))
.collect(Collectors.toSet());
or, to avoid the code duplication:
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> {
Stream<String> names = l.stream().map(StudentDetail::getName);
return l.size() <= 5? names: names.filter(s -> s.contains("A"));
})
.collect(Collectors.toSet());
Upvotes: 2
Reputation: 32046
You can try out processing the two halves based on the conditions using partitioningBy
.
Map<Boolean, List<StudentResponse>> greaterThanFive = studentResponse.stream()
.collect(Collectors.partitioningBy(sr -> sr.getDetails().size() > 5));
Set<String> names = Stream.concat(
greaterThanFive.get(Boolean.FALSE).stream()
.flatMap(sr -> sr.getDetails().stream())
.map(StudentDetail::getName),
greaterThanFive.get(Boolean.TRUE).stream()
.flatMap(sr -> sr.getDetails().stream())
.map(StudentDetail::getName)
.filter(name -> name.contains("A"))) // for details size more than 5
.collect(Collectors.toSet());
But there is no reason to choose it over a solution that can perform the partitioning on-the-fly.
Upvotes: 1
Reputation: 17299
Other way is using Supplier<T>
Supplier<Stream<List<StudentDetail>>> stuSup = ()-> studentResponse
.stream()
.map(StudentResponse::getDetails);
then perform a filter on it.
Stream<String> gtFive = stuSup.get()
.filter(d->d.size()>5)
.flatMap(List::stream)
.map(StudentDetail::getName())
.filter(s -> s.contains("A"));
and for less than five:
Stream<String> lteFive = stuSup.get()
.filter(d->d.size()<=5)
.flatMap(List::stream)
.map(StudentDetail::getName());
and finally, combine both of them.
Stream.concat(gtFive,lteFive).collect(toSet());
Upvotes: 1
Reputation: 4140
You can use Map.Entry
as a bag to collect all informations that are needed in the last filtering.
Set<String> name = studentResponse
.stream()
.flatMap(sr -> sr.getDetails().stream().map(
d -> Map.entry(sr.getDetails().size(), d.getName())))
.filter(e -> (e.getKey() <= 5) || (e.getKey() > 5 && e.getValue().contains("A")))
.map(Map.Entry::getValue)
.collect(Collectors.toSet());
Upvotes: 0