6

Представьте класс 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; 
    } 
    ... 
} 

Boolean статические методы, в сущности, аналогичные Булевозначный функции т.е. предикаты.

Мы можем построить предикат 2^(# предикатов) -by-# предикатов из предикатов. Например, если три предиката: ofWorkingAge, ofGoodCharacter, isQualified мы можем построить следующий 8-на-3 Таблица истинности:

T T T 
T T F 
T F T 
T F F 
F T T 
F T F 
F F T 
F F F 

Теперь мы хотим нанимать людей с желаемыми качествами. Пусть + указывают на то, что мы хотим рассмотреть возможность использования любого человека (т. Е. Установить свой флаг занятости на true) и - напротив.

T T T | + 
T T F | + 
T F T | + 
T F F | - 
F T T | + 
F T F | - 
F F T | - 
F F F | - 

Теперь представьте себе, что имеется коллекция объектов Person. Для каждого человека мы настраиваем свой флаг занятости в соответствии с тремя предикатами. Мы также обновить количество (это заставляет нас использовать всю таблицу истинности, а не только позитивы), так что данные 1000 человек, мы хотим, чтобы в конечном итоге что-то вроде:

T T T | + 100 
T T F | + 200 
T F T | + 50 
T F F | - 450 
F T T | + 50 
F T F | - 50 
F F T | - 50 
F F F | - 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++; 
} 

Это просто плохо.

Слегка надежное решение

Pass предиката (возможно, в массиве) и набор предложений к способу. Пусть метод генерирует соответствующую таблицу истинности. Прокрутите людей, установите их возможности для трудоустройства и верните множество графов.

Я могу видеть, как это можно сделать с помощью функциональных интерфейсов. This SO answer потенциально имеет значение.Вы можете изменить PrintCommand до IsQualified и пройти callCommand a Лицо вместо строки. Но это также кажется добрым неуклюжим, потому что тогда у нас должен быть новый файл интерфейса для каждого предиката, который мы придумаем.

Есть ли какой-либо другой способ 8-ий Java Java?

+1

ли желаемый результат таблица истинности со счетчиком для каждой строки? Или же желаемый результат, что все объекты 'Person' имеют правильно установленный флаг' useable', заданный произвольным числом предикатов? –

+0

Оба одновременно. :) Учитывая произвольное количество предикатов, правильно установите подходящий флаг и верните таблицу истинности со счетом для каждой строки. –

+0

Сколько предикатов нужно оценивать как истинное для «Человека», которое можно использовать? –

ответ

3

Начнут со списком предикатов у вас есть:

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

для отслеживания предиката является истинным или ложным, давайте присоединять имена их создание NamedPredicate класса:

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

(один может приложить BitSet или что-то подобное для эффективности, но String имена тоже прекрасны).

Теперь нам нужно сгенерировать таблицу истинности, которая представляет собой новый список предикатов с именами, такими как "T T F" и способными применить данную комбинацию исходных предикатов, отрицательных или нет. Это может быть легко сгенерирована с немного функционального программирования магии:

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

, как truthTable является Supplier<Stream>, вы можете использовать его столько раз, сколько вы хотите. Также обратите внимание, что все объекты NamedPredicate генерируются «на лету» по требованию, мы их не храним нигде. Давайте попробуем использовать этот поставщик:

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

Выход есть:

T T T 
T T F 
T F T 
T F F 
F T T 
F T F 
F F T 
F F F 

Теперь вы можете классифицировать persons коллекцию с помощью таблицы истинности, например, следующим образом:

Map<String,List<Person>> map = truthTable.get().collect(
    Collectors.toMap(np -> np.toString(), // Key is string like "T T F" 
     // 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<String> desired = Arrays.asList("T T T", "T T F", "T F T", "F T T"); 

В этом случае вы можете использовать сгенерированный ранее карту:

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

Спасибо за потрясающий ответ! Я начинающий лямбда-выражение. Можете ли вы объяснить, что означает 'p ->() -> Stream.of (...)'? Как передать параметр(), который не должен принимать какие-либо? –

+1

@knitty, 'p ->() -> Stream.of()' is 'p -> (() -> Stream.of())'. Это функция единственного аргумента 'p', который возвращает другую функцию, не принимающую никаких аргументов (поставщик-функция), которая, в свою очередь, производит данный поток при вызове. См. [Этот вопрос] (http://stackoverflow.com/q/32820722/4856258) для деталей. –

0

Я не уверен, если это то, что вы ищете, но вы можете использовать битовые операторы на ваших переменных ..

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.

2

В принципе, значение истина есть один бит, и вы всегда можете использовать целочисленное значение n бит для кодирования n значение истины.Затем интерпретация целочисленного значения как числа позволяет сопоставлять значения с комбинацией значений истинности с использованием линейной таблицы.

Таким образом, используя в кодировке индекса значение истинности/таблицы int, общая истина класс таблица может выглядеть следующим образом:

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(); 
    } 
} 

Ключевым моментом является расчет индекса int из значений истинности. Есть две версии. Сначала вычислите из явных логических значений для инициализации таблицы или запроса ее состояния, во-вторых, для фактического тестового объекта и списка применимых предикатов. Обратите внимание, что эти два метода учитываются в методах public static, так что они могут использоваться для альтернативных типов таблиц, например. массив примитивных значений. Единственное, что нужно сделать, это создать линейное хранилище для значений 2ⁿ, если у вас есть n предикаты, например. new int[1<<n], а затем используя эти методы index для определения входа для доступа к заданным значениям или фактического кандидата теста.

Экземпляры общего TruthTable могут быть использованы следующим образом:

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

 Смежные вопросы

  • Нет связанных вопросов^_^