limrain
limrain

Reputation: 61

Filter in Java when meeting conditions

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

Answers (4)

Holger
Holger

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

Naman
Naman

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

Hadi
Hadi

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

lczapski
lczapski

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

Related Questions