使用真值表过滤

想象一个带有布尔标志的Person类,该布尔标志指示该人是否可以使用 – 默认情况下设置为false。

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

现在假设有一些外部布尔方法作用于Person对象。 例如,考虑实用程序类中的静态布尔方法。

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

布尔静态方法本质上类似于布尔值函数,即谓词

我们可以用谓词构造一个2 ^(#谓词)-by-#谓词真值表。 例如,给定三个谓词: ofWorkingAgeofGoodCharacterisQualified我们可以构造以下8乘3真值表:

 TTT TTF TFT TFF FTT FTF FFT FFF 

我们现在想雇用具有理想品质的人。 让+表示我们希望考虑某人可就业(即将他们的就业能力标志设为 )和相反。

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

现在想象一下Person对象的集合。 对于每个人,我们根据三个谓词调整他们的就业能力标志。 我们还更新了一个计数 (这迫使我们使用整个真值表而不仅仅是积极的),所以给了1000个人我们希望最终得到类似的东西:

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

据推测,这可以被认为是用真值表过滤。 设置就业能力标志和更新计数是一个相当人为的例子,但你可以很容易地看到我们可能想要设置和更新更复杂的东西。

有没有办法优雅地做到这一点? 我可以想到两个解决方案:

笨重的解决方案

有一个巨大的手编码,否则如果,否则链。

 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++; } 

这很糟糕。

更智能的解决方案

将谓词(可能在数组中)和句子集合传递给方法。 让方法生成相应的真值表。 循环人,设置他们的就业能力,并返回一系列计数。

我可以看到如何通过function接口完成任务。 这个SO答案可能具有相关性。 您可以将PrintCommand更改为IsQualified并将callCommand传递给Person而不是字符串。 但这似乎也很笨重,因为我们必须为我们想出的每个谓词都有一个新的接口文件。

还有其他Java 8-ish方法吗?

让我们从您拥有的谓词列表开始:

 List> predicates = Arrays.> asList( PersonUtil::ofWorkingAge, PersonUtil::ofGoodCharacter, PersonUtil::isQualified); 

要跟踪哪个谓词为true或false,让我们为它们附加名称,创建NamedPredicate类:

 public static class NamedPredicate implements Predicate { final Predicate predicate; final String name; public NamedPredicate(Predicate 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); } } 

(为了提高效率,可以附加BitSet或类似的东西,但String名称也很好)。

现在我们需要生成一个真值表,它是一个新的谓词列表,其名称类似于"TTF"并且能够应用给定的源谓词组合,否定与否。 这可以通过一些函数式编程魔术轻松生成:

 Supplier>> truthTable = predicates.stream() // start with plain predicates .>>>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); 

由于truthTableSupplier ,您可以根据需要多次重复使用它。 另请注意,所有NamedPredicate对象都是按需生成的,我们不会将它们存储在任何地方。 让我们尝试使用这个供应商:

 truthTable.get().forEach(System.out::println); 

输出是:

 TTT TTF TFT TFF FTT FTF FFT FFF 

现在,您可以通过真值表对persons集合进行分类,例如,通过以下方式:

 Map> 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)); 

现在您可以轻松获得计数:

 map.forEach((k, v) -> System.out.println(k+" | "+v.size())); 

要更新可employable字段,我们需要知道如何指定所需的真值表。 让它成为这样的真值串的集合:

 Collection desired = Arrays.asList("TTT", "TTF", "TFT", "FTT"); 

在这种情况下,您可以使用以前生成的地图:

 desired.stream() .flatMap(k -> map.get(k).stream()) .forEach(person -> person.setEmployable(true)); 

基本上,真值是单个位,您总是可以使用n位的整数值来编码n个真值。 然后,将整数值解释为数字允许您使用线性表将值与真值的组合相关联。

因此,使用int编码的真值/表索引,通用真值表类可能如下所示:

 public class TruthTable { final List> predicates; final ArrayList values; @SafeVarargs public TruthTable(Predicate... 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< int index(T object, List> 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< a|b).getAsInt(); } public static  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< a|b).getAsInt(); } } 

关键点是从真值中计算int索引。 有两个版本。 首先,从显式布尔值计算初始化表或查询其状态,第二,计算实际测试对象和适用谓词列表。 请注意,这两种方法被分解为public static方法,以便它们可以用于替代表类型,例如原始值数组。 当你有n个谓词时,唯一要做的就是为2ⁿ值创建一个线性存储,例如new int[1< ,然后使用这些index方法确定给定值或实际测试候选者的访问条目。

通用TruthTable实例可以使用如下:

 TruthTable 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); 

我不确定这是否是您正在寻找的,但您可以在变量上使用按位运算符..

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

可能成为

 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 

其中最后一位代表WorkingAge / ofGoodCharacter / isQualified。