Let's start with the predicate list you have:
List<Predicate<Person>> predicates = Arrays.<Predicate<Person>> asList( PersonUtil::ofWorkingAge, PersonUtil::ofGoodCharacter, PersonUtil::isQualified);
To keep track of which predicate is true or false, let's attach names to them by creating a NamedPredicate class:
public static class NamedPredicate<T> implements Predicate<T> { final Predicate<T> predicate; final String name; public NamedPredicate(Predicate<T> predicate, String name) { this.predicate = predicate; this.name = name; } @Override public String toString() { return name; } @Override public boolean test(T t) { return predicate.test(t); } }
(you can attach a BitSet or something similar for efficiency, but String names are fine too.)
Now we need to generate a truth table, which is a new list of predicates with names such as "TTF" and capable of applying this combination of source predicates, negative or not. This can be easily generated using the magic of functional programming:
Supplier<Stream<NamedPredicate<Person>>> truthTable = predicates.stream() // start with plain predicates .<Supplier<Stream<NamedPredicate<Person>>>>map( // generate a supplier which creates a stream of // true-predicate and false-predicate p -> () -> Stream.of( new NamedPredicate<>(p, "T"), new NamedPredicate<>(p.negate(), "F"))) .reduce( // reduce each pair of suppliers to the single supplier // which produces a Cartesian product stream (s1, s2) -> () -> s1.get().flatMap(np1 -> s2.get() .map(np2 -> new NamedPredicate<>(np1.and(np2), np1+" "+np2)))) // no input predicates? Fine, produce empty stream then .orElse(Stream::empty);
as truthTable is a Supplier<Stream> , you can use it as many times as you want. Also note that all NamedPredicate objects NamedPredicate generated "on the fly" on demand, we do not store them anywhere. Try using this provider:
truthTable.get().forEach(System.out::println);
Output:
TTT TTF TFT TFF FTT FTF FFT FFF
Now you can classify the persons collection according to the truth table, for example, as follows:
Map<String,List<Person>> map = truthTable.get().collect( Collectors.toMap(np -> np.toString(),
Now you can easily get the calculations:
map.forEach((k, v) -> System.out.println(k+" | "+v.size()));
To update the employable field, we need to know how the desired truth table is indicated. Let it be a set of truth lines, like this:
Collection<String> desired = Arrays.asList("TTT", "TTF", "TFT", "FTT");
In this case, you can use the previously generated map:
desired.stream() .flatMap(k -> map.get(k).stream()) .forEach(person -> person.setEmployable(true));