Home > Enterprise >  How to elegantly filter a java stream repeatedly until a single result is found?
How to elegantly filter a java stream repeatedly until a single result is found?

Time:06-30

I have the following function which attempts to progressively narrow down an input collection until a single element is found, i.e. filtering is supposed to stop when a single item has been found as applying additional filters may result in no match at all.

public List<MyObject> determinePotentialCandidates(List<MyObject> allCandidates) {

        List<MyObject> candidates = allCandidates.stream()
                                                 .filter(this::firstCondition)
                                                 .toList();

        if (candidates.size() > 1) {

            candidates = candidates.stream()
                                   .filter(this::secondCondition)
                                   .toList();

            if (candidates.size() > 1) {

                candidates = candidates.stream()
                                       .filter(this::thirdCondition)
                                       .collect(Collectors.toList());
            }
            
            // ... and so on
        }

        logResult(candidates);
        return candidates;
    }

As this is becomes harder to read with each additional nesting level, I was wondering if there is a more concise way to write this.

Preferably the method should perform each filtering step at most once (although input size is small and filtering is inexpensive - this could potentially be ok to be performed multiple times for the same input) and contain a single exit point.

CodePudding user response:

You can put all the conditions into a List and loop over it, applying one filter on each iteration until there is only one element left.

List<Predicate<MyObject>> conditions = List.of(this::firstCondition, this::secondCondition, this::thirdCondition /*...*/ );
for(int i = 0; i < conditions.size() && allCandidates.size() > 1; i  )
    allCandidates = allCandidates.stream().filter(conditions.get(i)).toList();
return allCandidates;

CodePudding user response:

I think that you can apply the filter operation to the same first stream and it will give you the expected result :

  public List<MyObject> determinePotentialCandidates(List<MyObject> allCandidates) {
         List<MyObject> candidates = allCandidates.stream()
                                                  .filter(this::firstCondition)
                                                  .filter(this::secondCondition)
                                                  .filter(this::thirdCondition)
                                                  .collect(Collectors.toList());
             }
             // ... and so on
         }
          logResult(candidates);
         return candidates;
     }
  • Related