Как улучшить логику, чтобы проверить, соответствуют ли 4 логическим значениям некоторые случаи - PullRequest
0 голосов
/ 03 декабря 2018

У меня есть четыре bool значения:

bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;

Допустимые значения:

         Scenario 1 | Scenario 2 | Scenario 3
bValue1: true       | true       | true
bValue2: true       | true       | false
bValue3: true       | true       | false
bValue4: true       | false      | false

Так, например, этот сценарий неприемлем:

bValue1: false
bValue2: true
bValue3: true
bValue4: true

В настоящий момент я пришел с этим оператором if для обнаружения плохих сценариев:

if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
   ((bValue3 && (!bValue2 || !bValue1)) ||
   (bValue2 && !bValue1) ||
   (!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}

Можно ли улучшить / упростить эту логику оператора?

Ответы [ 30 ]

0 голосов
/ 04 декабря 2018

Рассмотрите возможность перевода ваших таблиц как можно напрямую в вашу программу.Управляйте программой на основе таблицы, вместо того, чтобы воспроизводить ее с помощью логики.

template<class T0>
auto is_any_of( T0 const& t0, std::initializer_list<T0> il ) {
  for (auto&& x:il)
    if (x==t0) return true;
  return false;
}

сейчас

if (is_any_of(
  std::make_tuple(bValue1, bValue2, bValue3, bValue4),
  {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false}
  }
))

это напрямую, насколько это возможно, кодирует вашу таблицу истинности в компилятор.

Живой пример .

Вы также можете использовать std::any_of напрямую:

using entry = std::array<bool, 4>;
constexpr entry acceptable[] = 
  {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false}
  };
if (std::any_of( begin(acceptable), end(acceptable), [&](auto&&x){
  return entry{bValue1, bValue2, bValue3, bValue4} == x;
}) {
}

компилятор может встроить код, исключить любую итерацию и построить свою собственнуюлогика для тебя.Между тем, ваш код точно отражает то, как вы решили проблему.

0 голосов
/ 03 декабря 2018

Мой предыдущий ответ уже принят, я добавляю что-то, что, на мой взгляд, читабельно, легко и в этом случае открыто для будущих изменений:

Начиная с ответа @ZdeslavVojkovic (который я считаю довольно хорошим)), Я придумал это:

#include <iostream>
#include <set>

//using namespace std;

int GetScenarioInt(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    return bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
}
bool IsValidScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    std::set<int> validScenarios;
    validScenarios.insert(GetScenarioInt(true, true, true, true));
    validScenarios.insert(GetScenarioInt(true, true, true, false));
    validScenarios.insert(GetScenarioInt(true, false, false, false));

    int currentScenario = GetScenarioInt(bValue1, bValue2, bValue3, bValue4);

    return validScenarios.find(currentScenario) != validScenarios.end();
}

int main()
{
    std::cout << IsValidScenario(true, true, true, false) << "\n"; // expected = true;
    std::cout << IsValidScenario(true, true, false, false) << "\n"; // expected = false;

    return 0;
}

Посмотрите на это на работе здесь

Ну, это "элегантное и удобное в обслуживании" (ИМХО) решение, к которому я обычно стремлюсьдля, но на самом деле, для случая с OP, мой предыдущий ответ «пучок ifs» лучше соответствует требованиям OP, даже если он не элегантный и не обслуживаемый.

0 голосов
/ 05 декабря 2018

Битовая операция выглядит очень чистой и понятной.

int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
    //satisfying condition
}
0 голосов
/ 04 декабря 2018

Это зависит от того, что они представляют.

Например, если 1 является ключом, а 2 и 3 - это два человека, которые должны согласиться (за исключением случаев, когда они согласны с NOT, для подтверждения необходим третий человек - 4 ), наиболее читаемым может быть:

1 &&
    (
        (2 && 3)   
        || 
        ((!2 && !3) && !4)
    )

отпопулярный запрос:

Key &&
    (
        (Alice && Bob)   
        || 
        ((!Alice && !Bob) && !Charlie)
    )
0 голосов
/ 06 декабря 2018

Мои 2 цента: объявите переменную сумму (целое число), чтобы

if(bValue1)
{
  sum=sum+1;
}
if(bValue2)
{
  sum=sum+2;
}
if(bValue3)
{
  sum=sum+4;
}
if(bValue4)
{
  sum=sum+8;
}

Проверили сумму по условиям, которые вы хотите, и все.Таким образом, вы можете легко добавить больше условий в будущем, чтобы их было легко читать.

0 голосов
/ 04 декабря 2018

Небольшое отклонение от точного ответа @ GianPaolo, которое некоторым может показаться более легким для чтения:

bool any_of_three_scenarios(bool v1, bool v2, bool v3, bool v4)
{
  return (v1 &&  v2 &&  v3 &&  v4)  // scenario 1
      || (v1 &&  v2 &&  v3 && !v4)  // scenario 2
      || (v1 && !v2 && !v3 && !v4); // scenario 3
}

if (any_of_three_scenarios(bValue1,bValue2,bValue3,bValue4))
{
  // ...
}
0 голосов
/ 07 декабря 2018

Просто личное предпочтение перед принятым ответом, но я бы написал:

bool valid = false;
// scenario 1
valid = valid || (bValue1 && bValue2 && bValue3 && bValue4);
// scenario 2
valid = valid || (bValue1 && bValue2 && bValue3 && !bValue4);
// scenario 3
valid = valid || (bValue1 && !bValue2 && !bValue3 && !bValue4);
0 голосов
/ 04 декабря 2018

Я не вижу ответов, в которых говорится об именах сценариев, хотя решение ОП именно так и делает.

Для меня лучше всего заключить комментарий того, что представляет собой каждый сценарий, в имя переменной илиимя функцииВы, скорее всего, проигнорируете комментарий, чем имя, и если ваша логика изменится в будущем, вы, скорее всего, измените имя, чем комментарий.Вы не можете реорганизовать комментарий.

Если вы планируете повторно использовать эти сценарии вне вашей функции (или, возможно, захотите), то создайте функцию, которая сообщает, что он оценивает (constexpr / noexcept необязательно, но рекомендуется):

constexpr bool IsScenario1(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && b4; }

constexpr bool IsScenario2(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && !b4; }

constexpr bool IsScenario3(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && !b2 && !b3 && !b4; }

По возможности сделайте эти методы класса (как в решении OP).Вы можете использовать переменные внутри своей функции, если не думаете, что будете повторно использовать логику:

const auto is_scenario_1 = bValue1 && bValue2 && bValue3 && bValue4;
const auto is_scenario_2 = bvalue1 && bvalue2 && bValue3 && !bValue4;
const auto is_scenario_3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

Скорее всего, компилятор определит, что если bValue1 имеет значение false, то все сценарии являются ложными.Не беспокойтесь о том, чтобы сделать это быстро, просто правильно и читабельно.Если вы профилируете свой код и обнаружите, что это узкое место, потому что компилятор сгенерировал неоптимальный код со значением -O2 или выше, попробуйте переписать его.

0 голосов
/ 07 декабря 2018

Принятый ответ хорошо, когда у вас есть только 3 случая, и где логика для каждого проста.

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

Вы создаете BaseValidator, который содержит ссылку на BaseValidator и метод validateи метод для вызова проверки на указанном валидаторе.

class BaseValidator {
    BaseValidator* nextValidator;

    public:
    BaseValidator() {
        nextValidator = 0;
    }

    void link(BaseValidator validator) {
        if (nextValidator) {
            nextValidator->link(validator);
        } else {
            nextValidator = validator;
        }
    }

    bool callLinkedValidator(bool v1, bool v2, bool v3, bool v4) {
        if (nextValidator) {
            return nextValidator->validate(v1, v2, v3, v4);
        }

        return false;
    }

    virtual bool validate(bool v1, bool v2, bool v3, bool v4) {
        return false;
    }
}

Затем вы создаете несколько подклассов, которые наследуются от BaseValidator, переопределяя метод validate с помощью логики, необходимой для каждого валидатора.

class Validator1: public BaseValidator {
    public:
    bool validate(bool v1, bool v2, bool v3, bool v4) {
        if (v1 && v2 && v3 && v4) {
            return true;
        }

        return nextValidator->callLinkedValidator(v1, v2, v3, v4);
    }
}

Тогда, используя это просто, создайте экземпляр каждого из ваших валидаторов и установите каждый из них как корень других:

Validator1 firstValidator = new Validator1();
Validator2 secondValidator = new Validator2();
Validator3 thirdValidator = new Validator3();
firstValidator.link(secondValidator);
firstValidator.link(thirdValidator);
if (firstValidator.validate(value1, value2, value3, value4)) { ... }

По сути, каждый случай валидации имеетсвой собственный класс, который отвечает за (а) определение, соответствует ли валидация этому случаю, и (б) отправку валидации кому-либо еще в цепочке, если это не так.

Обратите вниманиегоЯ не знаком с C ++.Я пытался сопоставить синтаксис из некоторых примеров, которые я нашел в Интернете, но если это не работает, относитесь к нему больше как к псевдокоду.У меня также есть полный рабочий пример Python, приведенный ниже, который можно использовать в качестве основы, если он предпочтителен.

class BaseValidator:
    def __init__(self):
        self.nextValidator = 0

    def link(self, validator):
        if (self.nextValidator):
            self.nextValidator.link(validator)
        else:
            self.nextValidator = validator

    def callLinkedValidator(self, v1, v2, v3, v4):
        if (self.nextValidator):
            return self.nextValidator.validate(v1, v2, v3, v4)

        return False

    def validate(self, v1, v2, v3, v4):
        return False

class Validator1(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator2(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator3(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and not v2 and not v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

firstValidator = Validator1()
secondValidator = Validator2()
thirdValidator = Validator3()
firstValidator.link(secondValidator)
firstValidator.link(thirdValidator)
print(firstValidator.validate(False, False, True, False))

Опять же, вы можете найти это перегиб для вашего конкретного примера, но он создает намного более чистый код, если вы в конечном итогес гораздо более сложным набором случаев, которые должны быть выполнены.

0 голосов
/ 03 декабря 2018

Как подсказывает mch, вы можете сделать:

if(!((bValue1 && bValue2 && bValue3) || 
  (bValue1 && !bValue2 && !bValue3 && !bValue4))
)

, где первая строка покрывает два первых хороших случая, а вторая строка покрывает последний.

Live Demo , где я играл, и он проходит ваши дела.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...