user731136
user731136

Reputation:

Incremental conditions to filter elements of given collection

I would like to know an scalable and generic way to apply incremental filters to a given collection, without losing the cause of the failure, I mean, which filter does not match.

The following code is a simplification of the existing one (it has more conditions and error messages):

public class FailureCause {

  public static void main(String[] args) {
    validateAtRawWay(asList(201, 300, 450));
    validateAtRawWay(asList(20, 30, 40));
    validateAtRawWay(asList(400, 1600));
    validateAtRawWay(asList(10, 25, 36, 201, 1600));
  }

  public static void validateAtRawWay(List<Integer> sourceToCheck) {
    System.out.println("Initial elements: " + sourceToCheck);

    if (!sourceToCheck.stream()
            .filter(FailureCause.upTo100())
            .findAny()
            .isPresent()) {
        System.out.println("ERROR: no numbers less or equals than 100 were found");
        return;
    }
    if (!sourceToCheck.stream()
            .filter(FailureCause.isPerfectSquare())
            .findAny()
            .isPresent()) {
        System.out.println("ERROR: no perfect square numbers were found");
        return;
    }
    List<Integer> validResults = sourceToCheck.stream()
            .filter(FailureCause.upTo100())
            .filter(FailureCause.isPerfectSquare())
            .collect(toList());
    System.out.println("Valid data: " + validResults);
  }

  public static Predicate<Integer> upTo100() {
    return i -> i <= 100;
  }

  public static Predicate<Integer> isPerfectSquare() {
    return i -> {
        double sq = Math.sqrt(i);
        return (sq - Math.floor(sq)) == 0;
    };
  }
}

However, with that approach the collection has to be iterated several times to know the error and finally, get the suitable elements that verify all conditions.

As alternative to the above approach I have developed the following class to store: the filters, its importance order, error message and if there was an element that verified it.

public class ConditionToMatch<E> implements Comparable<ConditionToMatch> {

  // To omit getter/setter
  public int order;
  public Predicate<E> condition;
  public boolean wasFound;
  public String errorMessage;

  public ConditionToMatch (int order, Predicate<E> condition, boolean wasFound, String errorMessage) {
    this.order = order;
    this.condition = condition;
    this.wasFound = wasFound;
    this.errorMessage = errorMessage;
  }

  @Override
  public int compareTo(ConditionToMatch o) {
    return ofNullable(o)
            .map(ext -> Integer.compare(order, ext.order))
            .orElse(-1);
  }
} 

Adding this use case, to the previous FailureCause class:

public class FailureCause {

  public static void main(String[] args) {
    ...

    validateWithConditionClass(asList(201, 300, 450));
    validateWithConditionClass(asList(20, 30, 40));
    validateWithConditionClass(asList(400, 1600));
    validateWithConditionClass(asList(10, 25, 36, 201, 1600));
  }
  ...

  public static void validateWithConditionClass(List<Integer> sourceToCheck) {
    System.out.println("Initial elements: " + sourceToCheck);

    ConditionToMatch<Integer> isUpTo100Condition = isUpTo100ConditionToMatch();
    ConditionToMatch<Integer> isPerfectSquareCondition = isPerfectSquareConditionToMatch();

    // Used to get the "most important" filter has failed
    PriorityQueue<ConditionToMatch> priorizeValidations = new PriorityQueue<>(asList(isUpTo100Condition, isPerfectSquareCondition));

    List<Integer> validResults = sourceToCheck.stream()
            .map(s -> {
                if (isUpTo100Condition.condition.test(s)) {
                    isUpTo100Condition.wasFound = true;
                }
                if (isPerfectSquareCondition.condition.test(s)) {
                    isPerfectSquareCondition.wasFound = true;
                }
                return s;
            })
            .filter(isUpTo100Condition.condition)
            .filter(isPerfectSquareCondition.condition)
            .collect(toList());

    if (validResults.isEmpty()) {
        Iterator<ConditionToMatch> value = priorizeValidations.iterator();
        // To know the first "most important" filter that failed
        while (value.hasNext()) {
            ConditionToMatch current = value.next();
            if (!current.wasFound) {
                System.out.println(current.errorMessage);
                break;
            }
        }
    }
    else {
        System.out.println("Valid data: " + validResults);
    }
  }
  ...  

  public static ConditionToMatch<Integer> isUpTo100ConditionToMatch() {
    return new ConditionToMatch<>(1, FailureCause.upTo100(), false, "ERROR: no numbers less or equals than 100 were found");
  }

  public static ConditionToMatch<Integer> isPerfectSquareConditionToMatch() {
    return new ConditionToMatch<>(2, FailureCause.isPerfectSquare(), false, "ERROR: no perfect square numbers were found");
  }
}

My question is: is there a better and scalable way to do it?

Upvotes: 1

Views: 304

Answers (2)

Eritrean
Eritrean

Reputation: 16498

I would adapt your first approach and avoid streams there.

import static java.util.function.Predicate.not;
...

public static void validateAtRawWay(List<Integer> sourceToCheck) {
    List<Integer> copy = new ArrayList<>(sourceToCheck);
    System.out.println("Initial elements: " + copy);
    
    copy.removeIf(not(upTo100()));
    if (copy.isEmpty()) {
        System.out.println("ERROR: no numbers less or equals than 100 were found");
        return;
    }
    
    copy.removeIf(not(isPerfectSquare()));
    if (copy.isEmpty()) {
        System.out.println("ERROR: no perfect square numbers were found");
        return;
    }
    
    List<Integer> validResults = copy;
    System.out.println("Valid data: " + validResults);
}

I don't know how many conditions you have to check. Even if you have to repeat yourself often with this example, i think it is more legible. You could also extract a method that accepts a list and a predicate to reduce the repeated blocks.

Upvotes: 1

Nicolas Bousquet
Nicolas Bousquet

Reputation: 4000

You can do it more easily by setting the boolean value directly in ConditionToMatch class:

public class ConditionToMatch<E> implements Predicate<E> {
  public Predicate<E> condition;
  public boolean neverMatchedAnything = true;
  public String errorMessage;

  public ConditionToMatch (Predicate<E> condition, int order, String errorMessage) {
    this.order = order;
    this.condition = condition;
    this.errorMessage = errorMessage;
  }

  @Override
  public boolean test(E e) {
     boolean value = condition.test(e)
    if (value) {
      neverMatchedAnything = false;
    }
    return value;
  }
}

Then you can write a generic checker:

public static void validateWithConditionClass(List<Integer> sourceToCheck, List<ConditionToMatch<Integer>> conditions) {
    System.out.println("Initial elements: " + sourceToCheck);
    conditions.sort((c1, c2) -> c2.order - c1.order);

    Stream<Integer> stream sourceToCheck.stream();
    for (ConditionToMatch<Integer> condition : conditions) 
      stream = stream.filter(condition)
    }
    List<Integer> results = streams.collect();

    for (ConditionToMatch<Integer> condition : conditions) 
      if (condition.neverMatchedAnything) {
        System.out.println(current.errorMessage);
        break;
      }
    }
  }

And the specific implementation for your 2 checkers:

public static void validateWithConditionClass(List<Integer> sourceToCheck) {
    List<ConditionToMatch<Integer>> conditions = Arrays.toList({isUpTo100ConditionToMatch(), isPerfectSquareConditionToMatch()});
    validateWithConditionClass(sourceToCheck, conditions);
  }

And of course the functions to create the conditions:

  public static ConditionToMatch<Integer> isUpTo100ConditionToMatch() {
return new ConditionToMatch<>(FailureCause.upTo100(), 1, "ERROR: no numbers less or equals than 100 were found");
  }

  public static ConditionToMatch<Integer> isPerfectSquareConditionToMatch() {
return new ConditionToMatch<>( FailureCause.isPerfectSquare(), 2, "ERROR: no perfect square numbers were found");
  }

Upvotes: 1

Related Questions