Truth Table Filtering

Imagine a Person class with a boolean flag indicating whether a person can be used - the default value is false.

public class Person{ boolean employable = false; ... } 

Now imagine that there are some external logical methods that act on Person objects. For example, consider static logical methods in a utility class.

 public class PersonUtil{ public static boolean ofWorkingAge(Person p){ if(p.getAge() > 16) return true; return false; } ... } 

Logical static methods are essentially similar to boolean significant functions, i.e. predicates.

We can construct predicates 2 ^ (# predicates) -by- # predicates from predicates. For example, given three predicates: ofWorkingAge, ofGoodCharacter, isQualified, we can build the following 8 by 3 truth table:

 TTT TTF TFT TFF FTT FTF FFT FFF 

Now we want to use people with the desired qualities. Let + indicate that we want to consider someone who can be used (i.e., set the usability flag to true) and - .

 TTT | + TTF | + TFT | + TFF | - FTT | + FTF | - FFT | - FFF | - 

Now imagine a collection of Person objects. For each person, we customize our employment flag in accordance with three predicates. We are also updating the account (this forces us to use the entire truth table, not just the positive ones), so, given 1000 people, we want to get something like:

 TTT | + 100 TTF | + 200 TFT | + 50 TFF | - 450 FTT | + 50 FTF | - 50 FFT | - 50 FFF | - 50 

Presumably, this can be considered as filtering with truth tables. Setting busy flags and counting updates is a pretty far-fetched example, but you can easily see how we can install and update much more complex things instead.

Question

Is there any way to elegantly do this? I can present two solutions:

Clunky solution

Have a giant manual encoding of if, else if, else chain.

 if(ofWorkingAge && ofGoodCharacter && isQualified){ c1++; p.setEmployable(true) } else if(ofWorkingAge && ofGoodCharacter && !isQualified){ c2++; p.setEmployable(true) } ... else if(!ofWorkingAge && !ofGoodCharacter && isQualified){ c7++; } else{ c8++; } 

This is just bad.

A little reasonable decision

Skip predicates (possibly in an array) and a set of sentences for the method. Let the method generate the corresponding truth table. Scroll through the people, set their job opportunities and return the many graphs.

I see how this can be done using functional interfaces. This "SO" answer potentially matters. You can change PrintCommand to IsQualified and pass callCommand Person instead of a string. But that also seems kind of awkward, because then we have to have a new interface file for each predicate that we come up with.

Is there any other way to run 8th Java?

+6
source share
3 answers

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(), // Key is string like "TTF" // Value is the list of persons for which given combination is true np -> persons.stream().filter(np).collect(Collectors.toList()), // Merge function: actually should never happen; // you may throw assertion error here instead (a, b) -> a, // Use LinkedHashMap to preserve an order LinkedHashMap::new)); 

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)); 
+3
source

Basically, the truth value is one bit, and you can always use the integer value n bits to encode n truth values. Then, interpreting the integer value as a number, you can match the values ​​with a combination of truth values ​​using a linear table.

Thus, using an int encoded truth index / table table, the general truth table class can look like this:

 public class TruthTable<O,V> { final List<? extends Predicate<? super O>> predicates; final ArrayList<V> values; @SafeVarargs public TruthTable(Predicate<? super O>... predicates) { int size=predicates.length; if(size==0 || size>31) throw new UnsupportedOperationException(); this.predicates=Arrays.stream(predicates) .map(Objects::requireNonNull).collect(Collectors.toList()); values=new ArrayList<>(Collections.nCopies(1<<size, null)); } public V get(O testable) { return values.get(index(testable, predicates)); } public V get(boolean... constant) { if(constant.length!=predicates.size()) throw new IllegalArgumentException(); return values.get(index(constant)); } public V set(V value, boolean... constant) { if(constant.length!=predicates.size()) throw new IllegalArgumentException(); return values.set(index(constant), value); } public static <T> int index(T object, List<? extends Predicate<? super T>> p) { int size=p.size(); if(size==0 || size>31) throw new UnsupportedOperationException(); return IntStream.range(0, size).map(i->p.get(i).test(object)? 1<<i: 0) .reduce((a,b) -> a|b).getAsInt(); } public static <T> int index(boolean... values) { int size=values.length; if(size==0 || size>31) throw new UnsupportedOperationException(); return IntStream.range(0, size).map(i->values[i]? 1<<i: 0) .reduce((a,b) -> a|b).getAsInt(); } } 

The key is to calculate the int index from the truth values. There are two versions. First, calculate from explicit logical values ​​to initialize the table or query its state, and secondly, for the actual test object and the list of applicable predicates. Note that these two methods are taken into account in public static methods, so that they can be used for alternative table types, for example. array of primitive values. The only thing to do is create a linear storage for 2ⁿ values ​​when you have n predicates, for example. new int[1<<n] , and then using these index methods to determine the input to access the given values ​​or the actual test candidate.

Common TruthTable can be used as follows:

 TruthTable<Person,Integer> scoreTable=new TruthTable<>( PersonUtil::ofWorkingAge, PersonUtil::ofGoodCharacter, PersonUtil::isQualified); scoreTable.set(+100, true, true, true); scoreTable.set(+200, true, true, false); scoreTable.set(+50, true, false, true); scoreTable.set(-450, true, false, false); scoreTable.set(+50, false, true, true); scoreTable.set(-50, false, true, false); scoreTable.set(-50, false, false, true); scoreTable.set(-50, false, false, false); Person p = … int score = scoreTable.get(p); 
+2
source

I'm not sure if this is what you are looking for, but you can use bitwise operators for your variables.

 if(ofWorkingAge && ofGoodCharacter && isQualified){ c1++; p.setEmployable(true) } 

can be

 int combined = 0b00000000; combined |= ofWorkingAge ? 0b00000100 : 0b00000000; combined |= ofGoodCharacter ? 0b00000010 : 0b00000000; combined |= isQualified ? 0b00000001 : 0b00000000; switch (combined){ case 0b00000111: c1++; p.setEmployable(true) break; case 0b00000110: // etc 

where the last bits represent the value of WorkingAge / ofGoodCharacter / isQualified.

0
source

All Articles