Как улучшить логику, чтобы проверить, соответствуют ли четыре логических значения некоторым случаям

У меня четыре значения 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
}

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

Ответ 1

Я бы стремился к удобочитаемости: у вас есть только 3 сценария, разберитесь с ними с 3 отдельными ifs:

bool valid = false;
if (bValue1 && bValue2 && bValue3 && bValue4)
    valid = true; //scenario 1
else if (bValue1 && bValue2 && bValue3 && !bValue4)
    valid = true; //scenario 2
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
    valid = true; //scenario 3

Легко читать и отлаживать, ИМХО. Кроме того, вы можете назначить переменную whichScenario при выполнении whichScenario if.

Только с 3 сценариями я бы не пошел с чем-то таким "если первые 3 значения верны, я могу избежать проверки четвертого значения": это усложнит чтение и поддержку вашего кода.

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

Если ваша логика усложняется, отбросьте этот код и подумайте об использовании чего-то большего для хранения различных доступных сценариев (как предлагает Zladeck).

Я действительно люблю первое предложение, данное в этом ответе: легко читаемое, не подверженное ошибкам, поддерживаемое

(Почти) не по теме:

Я не пишу много ответов здесь, в StackOverflow. Это действительно забавно, что принятый выше ответ является безусловно самым ценным ответом в моей истории (никогда не имел больше 5-10 голосов, прежде чем я думаю), хотя на самом деле это не то, что я обычно считаю "правильным" способом сделать это.

Но простота часто является "правильным способом сделать это", многие люди, кажется, думают об этом, и я должен думать об этом больше, чем я :)

Ответ 2

Я бы стремился к простоте и удобочитаемости.

bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
bool scenario2 = bValue1 && bValue2 && bValue3 && !bValue4;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

if (scenario1 || scenario2 || scenario3) {
    // Do whatever.
}

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

bool scenario1or2 = bValue1 && bValue2 && bValue3;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

if (scenario1or2 || scenario3) {
    // Do whatever.
}

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

Ответ 3

Мы можем использовать карту Карно и сводить ваши сценарии к логическому уравнению. Я использовал карточный решатель Online Karnaugh с цепью для 4 переменных.

enter image description here

Это дает:

enter image description here

Меняя A, B, C, D на bValue1, bValue2, bValue3, bValue4, это не что иное, как:

bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4

Итак, ваш оператор if:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Карты Карно особенно полезны, когда у вас много переменных и много условий, которые должны оцениваться как true.
  • После сокращения true сценариев до логического уравнения добавление соответствующих комментариев, указывающих true сценарии, является хорошей практикой.

Ответ 4

Реальный вопрос здесь: что происходит, когда другой разработчик (или даже автор) должен изменить этот код несколько месяцев спустя.

Я бы предложил моделировать это как битовые флаги:

const int SCENARIO_1 = 0x0F; // 0b1111 if using c++14
const int SCENARIO_2 = 0x0E; // 0b1110
const int SCENARIO_3 = 0x08; // 0b1000

bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = false;
bool bValue4 = false;

// boolean -> int conversion is covered by standard and produces 0/1
int scenario = bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
bool match = scenario == SCENARIO_1 || scenario == SCENARIO_2 || scenario == SCENARIO_3;
std::cout << (match ? "ok" : "error");

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

int scenarios[3][4] = {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false},
};

int main()
{
  bool bValue1 = true;
  bool bValue2 = false;
  bool bValue3 = true;
  bool bValue4 = true;
  bool match = false;

  // depending on compiler, prefer std::size()/_countof instead of magic value of 4
  for (int i = 0; i < 4 && !match; ++i) {
    auto current = scenarios[i];
    match = bValue1 == current[0] && 
            bValue2 == current[1] && 
            bValue3 == current[2] && 
            bValue4 == current[3];
  }

  std::cout << (match ? "ok" : "error");
}

Ответ 5

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

Начиная с ответа @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;
}

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

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

Ответ 6

Я также хотел бы представить другой подход.

Моя идея состоит в том, чтобы преобразовать bools в целое число, а затем сравнить с помощью вариативных шаблонов:

unsigned bitmap_from_bools(bool b) {
    return b;
}
template<typename... args>
unsigned bitmap_from_bools(bool b, args... pack) {
    return (bitmap_from_bools(b) << sizeof...(pack)) | bitmap_from_bools(pack...);
}

int main() {
    bool bValue1;
    bool bValue2;
    bool bValue3;
    bool bValue4;

    unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);

    if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) {
        //bad scenario
    }
}

Обратите внимание, как эта система может поддерживать до 32 переменных в качестве входных данных. замена unsigned с unsigned long long (или uint64_t) увеличивает поддержку до 64 случаев. Если вам не нравится if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u), вы также можете использовать еще один метод вариационных шаблонов:

bool equals_any(unsigned target, unsigned compare) {
    return target == compare;
}
template<typename... args>
bool equals_any(unsigned target, unsigned compare, args... compare_pack) {
    return equals_any(target, compare) ? true : equals_any(target, compare_pack...);
}

int main() {
    bool bValue1;
    bool bValue2;
    bool bValue3;
    bool bValue4;

    unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);

    if (!equals_any(summary, 0b1111u, 0b1110u, 0b1000u)) {
        //bad scenario
    }
}

Ответ 7

Вот упрощенная версия:

if (bValue1&&(bValue2==bValue3)&&(bValue2||!bValue4)) {
    // acceptable
} else {
    // not acceptable
}

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


Обновление: MSalters в комментариях нашел еще более простое выражение:

if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...

Ответ 8

Я не вижу никаких ответов, говорящих о названии сценариев, хотя решение OP делает именно это.

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

Если вы планируете повторно использовать эти сценарии за пределами вашей функции (или, возможно, захотите), а затем сделать функцию, которая говорит, что он оценивает ( 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 является ложным, все сценарии являются ложными. Не беспокойтесь о том, чтобы сделать его быстрым, просто правильным и читаемым. Если вы просматриваете свой код и считаете это узким местом, потому что компилятор генерирует субоптимальный код в -O2 или выше, попробуйте переписать его.

Ответ 9

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

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

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

Ответ 10

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

В итоге я решил добавить три новых "сценария" boolean метода:

bool CChristianLifeMinistryValidationDlg::IsFirstWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) && 
           !INCLUDE_ITEM2(pEntry) && 
           !INCLUDE_ITEM3(pEntry) && 
           !INCLUDE_ITEM4(pEntry));
}

bool CChristianLifeMinistryValidationDlg::IsSecondWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) &&
            INCLUDE_ITEM2(pEntry) &&
            INCLUDE_ITEM3(pEntry) &&
            INCLUDE_ITEM4(pEntry));
}

bool CChristianLifeMinistryValidationDlg::IsOtherWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) && 
            INCLUDE_ITEM2(pEntry) && 
            INCLUDE_ITEM3(pEntry) && 
           !INCLUDE_ITEM4(pEntry));
}

Затем я смог применить эту свою мою процедуру проверки следующим образом:

if (!IsFirstWeekStudentItems(pEntry) && !IsSecondWeekStudentItems(pEntry) && !IsOtherWeekStudentItems(pEntry))
{
    ; Error
}

В моем реальном приложении 4 значения bool фактически извлекаются из DWORD который имеет 4 значения, закодированные в нем.

Еще раз спасибо.

Ответ 11

AC/C++

bool scenario[3][4] = {{true, true, true, true}, 
                        {true, true, true, false}, 
                        {true, false, false, false}};

bool CheckScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    bool temp[] = {bValue1, bValue2, bValue3, bValue4};
    for(int i = 0 ; i < sizeof(scenario) / sizeof(scenario[0]); i++)
    {
        if(memcmp(temp, scenario[i], sizeof(temp)) == 0)
            return true;
    }
    return false;
}

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

Ответ 12

Легко заметить, что первые два сценария похожи - они разделяют большинство условий. Если вы хотите выбрать, в каком сценарии вы сейчас находитесь, вы можете написать его так (это измененное решение @gian-paolo):

bool valid = false;
if(bValue1 && bValue2 && bValue3)
{
    if (bValue4)
        valid = true; //scenario 1
    else if (!bValue4)
        valid = true; //scenario 2
}
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
    valid = true; //scenario 3

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

bool valid = false;
if(bValue1)
{
    if(bValue2 && bValue3)
    {
        if (bValue4)
            valid = true; //scenario 1
        else if (!bValue4)
            valid = true; //scenario 2
    }
    else if (!bValue2 && !bValue3 && !bValue4)
        valid = true; //scenario 3
}

Более того, теперь вы можете ясно видеть, что bValue2 и bValue3 несколько связаны друг с другом - вы можете извлечь их состояние для некоторых внешних функций или переменных с более подходящим именем (это не всегда легко или целесообразно):

bool valid = false;
if(bValue1)
{
    bool bValue1and2 = bValue1 && bValue2;
    bool notBValue1and2 = !bValue2 && !bValue3;
    if(bValue1and2)
    {
        if (bValue4)
            valid = true; //scenario 1
        else if (!bValue4)
            valid = true; //scenario 2
    }
    else if (notBValue1and2 && !bValue4)
        valid = true; //scenario 3
}

У этого есть некоторые преимущества и недостатки:

  • условия меньше, поэтому легче рассуждать о них,
  • легче сделать приятное переименование, чтобы сделать эти условия более понятными,
  • но они требуют понимания сферы действия,
  • более того, он более жесткий

Если вы прогнозируете, что будут изменения в вышеприведенной логике, вы должны использовать более простой подход, представленный @gian-paolo.

В противном случае, если эти условия будут хорошо установлены и являются своего рода "твердыми правилами", которые никогда не изменятся, рассмотрим мой последний фрагмент кода.

Ответ 13

Каждый ответ слишком сложный и трудный для чтения. Лучшим решением для этого является оператор switch(). Он читается и упрощает добавление/изменение дополнительных случаев. Компиляторы хорошо подходят для оптимизации операторов switch().

switch( (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1) )
{
    case 0b1111:
        // scenario 1
        break;

    case 0b0111:
        // scenario 2
        break;

    case 0b0001:
        // scenario 3
        break;

    default:
        // fault condition
        break;
}

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

Ответ 14

Небольшое отклонение от тонкого ответа @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))
{
  // ...
}

Ответ 15

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

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

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

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

Ответ 16

Я бы также использовал быстрые переменные для ясности. Как отмечалось ранее, сценарий 1 равен сценарию 2, поскольку значение bValue4 не влияет на правду этих двух сценариев.

bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)

то ваше выражение beomes:

if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
     // do something
}
else
{
    // There is some error
}

Предоставление значимых имен переменным MAJORTRUE и MAJORFALSE (а также фактически vValue * vars) очень помогло бы с удобочитаемостью и обслуживанием.

Ответ 17

Сосредоточьтесь на читаемости проблемы, а не на конкретном выражении "если".

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

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

#include <iostream>
#include <vector>
using namespace std;

// These values would likely not come from a single struct in real life
// Instead, they may be references to other booleans in other systems
struct Values
{
    bool bValue1; // These would be given better names in reality
    bool bValue2; // e.g. bDidTheCarCatchFire
    bool bValue3; // and bDidTheWindshieldFallOff
    bool bValue4;
};

class Scenario
{
public:
    Scenario(Values& values)
    : mValues(values) {}

    virtual operator bool() = 0;

protected:
    Values& mValues;    
};

// Names as examples of things that describe your "scenarios" more effectively
class Scenario1_TheCarWasNotDamagedAtAll : public Scenario
{
public:
    Scenario1_TheCarWasNotDamagedAtAll(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && mValues.bValue2
        && mValues.bValue3
        && mValues.bValue4;
    }
};

class Scenario2_TheCarBreaksDownButDidntGoOnFire : public Scenario
{
public:
    Scenario2_TheCarBreaksDownButDidntGoOnFire(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && mValues.bValue2
        && mValues.bValue3
        && !mValues.bValue4;
    }   
};

class Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere : public Scenario
{
public:
    Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && !mValues.bValue2
        && !mValues.bValue3
        && !mValues.bValue4;
    }   
};

Scenario* findMatchingScenario(std::vector<Scenario*>& scenarios)
{
    for(std::vector<Scenario*>::iterator it = scenarios.begin(); it != scenarios.end(); it++)
    {
        if (**it)
        {
            return *it;
        }
    }
    return NULL;
}

int main() {
    Values values = {true, true, true, true};
    std::vector<Scenario*> scenarios = {
        new Scenario1_TheCarWasNotDamagedAtAll(values),
        new Scenario2_TheCarBreaksDownButDidntGoOnFire(values),
        new Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(values)
    };

    Scenario* matchingScenario = findMatchingScenario(scenarios);

    if(matchingScenario)
    {
        std::cout << matchingScenario << " was a match" << std::endl;
    }
    else
    {
        std::cout << "No match" << std::endl;
    }

    // your code goes here
    return 0;
}

Ответ 18

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

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

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

по популярному запросу:

Key &&
    (
        (Alice && Bob)   
        || 
        ((!Alice && !Bob) && !Charlie)
    )

Ответ 19

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

int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
    //satisfying condition
}

Ответ 20

Для ясности я обозначаю a, b, c, d и A, B, C, D для дополнений

bValue1 = a (!A)
bValue2 = b (!B)
bValue3 = c (!C)
bValue4 = d (!D)

Уравнение

1 = abcd + abcD + aBCD
  = a (bcd + bcD + BCD)
  = a (bc + BCD)
  = a (bcd + D (b ^C))

Используйте любые уравнения, которые вам подходят.

Ответ 21

If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1 всегда должно быть истинным
  • b2 всегда должен равняться b3
  • и b4 не может быть ложным, если b2 (и b3) истинны

просто

Ответ 22

Только личное предпочтение по поводу принятого ответа, но я бы написал:

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

Ответ 23

Во-первых, если вы можете только изменить проверку сценария, я бы сосредоточился на удобочитаемости и просто обернул проверку функцией, чтобы вы могли просто вызвать if(ScenarioA()).


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

public class Options {
  public const bool A = 2; // 0001
  public const bool B = 4; // 0010
  public const bool C = 16;// 0100
  public const bool D = 32;// 1000
//public const bool N = 2^n; (up to n=32)
}

...

public isScenario3(int options) {
  int s3 = Options.A | Options.B | Options.C;
  // for true if only s3 options are set
  return options == s3;
  // for true if s3 options are set
  // return options & s3 == s3
}

Это позволяет выразить сценарии так же просто, как перечисление того, что является его частью, позволяет использовать оператор switch для перехода в правильное состояние и путать разработчиков-разработчиков, которые раньше этого не видели. (С# RegexOptions использует этот шаблон для установки флагов, я не знаю, есть ли библиотека библиотеки c++)

Ответ 24

Вложенные, if может быть проще читать для некоторых людей. Вот моя версия

bool check(int bValue1, int bValue2, int bValue3, int bValue4)
{
  if (bValue1)
  {
    if (bValue2)
    {
      // scenario 1-2
      return bValue3;
    }
    else
    {
      // scenario 3
      return !bValue3 && !bValue4;
    }
  }

  return false;
}

Ответ 25

"Самое простое" решение, которое я придумал, заключается в следующем:

bool valid = bValue1 && (bValue2 == bValue3) && (bValue2 ? true : !bValue4);

В принципе, я уменьшил раздувание таким образом:

  1. bValue1 всегда должно быть true
  2. bValue2 и bValue3 должны всегда быть одинаковыми
  3. bValue4 должен быть false тогда и только тогда, когда bValue2 и bValue3 являются ложными

Переход по частям (с очевидным комментарием):

  • bValue1 && гарантирует, что bValue1 всегда true Если это не так, оценка короткого замыкания перестает оцениваться, потому что false && [..] всегда false
  • (bValue2 == bValue3) && гарантирует, что они одинаковы (оба false или оба negative)
  • (bValue2? true: !bValue4) гарантирует, что значение bValue4 не имеет значения, если значение bValue2 true. В соответствии с указанными bValue4 должен быть false если bValue2 и bValue3 являются ложными.

Как это работает?
Оператор trenary "возвращает" результат, основанный на условии.

Рассмотрим следующий код:

// using the operator
bool value = condition ? true : false;

// using an if
bool value;
if(condition)
{
     value = true;
}
else
{
    value = false;
}

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

Чтение bValue2? true: !bValue4 bValue2? true: !bValue4 как вопрос можно рассматривать как:
Является ли bValue2 true? Если да, то дайте мне true, иначе дайте мне !bValue4.

Поскольку это оператор присваивания, вы можете сделать следующее:

string type = Chocolate.hasType() ? Chocolate.getType() : "";

Ответ 26

Я согласен с другими ответами, указывающими, что код должен быть чистым и читаемым. Имея это в виду, вы можете сделать несколько маршрутов для достижения чистого и читаемого кода, который эффективно выполняет задачу. Я хотел бы рассмотреть возможность использования массивов для достижения этой задачи, поскольку она обширна и легко читается. Чтобы доказать свою точку зрения, я являюсь разработчиком C# с минимальным опытом работы на C++ языке (на самом деле мне пришлось Google, как массивы создаются в C++), и протестировал этот метод с помощью ideone.com.

Я предпочитаю один массив, называемый scenario и двумерный массив, содержащий приемлемые сценарии, называемые acceptableScenarios сценариями.

#include <iostream>
using namespace std;

const int WIDTH = 3;
const int HEIGHT = 4;
bool acceptableScenarios[WIDTH][HEIGHT] = {
    { true, true, true, true },
    { true, true, true, false },
    { true, false, false, false }
};
int main() {
    bool scenario[4] = { true, false, false, false };
    return 0;
}

Затем вы можете быстро сравнить и расширить цикл:

bool IsScenarioAcceptable(bool scenario[]) {
    for (int asi = 0; asi < WIDTH; asi++) {
        bool match = true;
        for (int si = 0; si < HEIGHT; si++)
            match &= scenario[si] == acceptableScenarios[asi][si];

        if (match)
            return true;
    }
    return false;
}
int main() {
    bool scenario[4] = { true, false, false, false };
    bool acceptable = IsScenarioAcceptable(scenario);
 
    if (acceptable)
        cout << "The supplied scenario is acceptable.";
    else
        cout << "The supplied scenario is unacceptable.";
 
    return 0;
}

Это позволяет вам иметь как можно больше приемлемых сценариев; хотя у вас всегда будет только так много комбинаций, которые могут быть приемлемыми, в которых максимальный уровень составляет 100%.

Ответ 27

попробуйте присвоить логическим значениям четырехзначное число (0010 или 0001) и использовать метод str.slice(), чтобы увидеть, является ли переменная тем, что вы хотите

Ответ 28

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

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

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

Ответ 29

Вам не придется беспокоиться о недопустимых комбинациях логических флагов, если вы избавитесь от логических флагов.

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

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

У вас явно есть три состояния (сценарии). Было бы лучше смоделировать это и вывести логические свойства из этих состояний, а не наоборот.

enum State
{
    scenario1,
    scenario2,
    scenario3,
};

inline bool isValue1(State s)
{
    // (Well, this is kind of silly.  Do you really need this flag?)
    return true;
}

inline bool isValue2(State s)
{
    switch (s)
    {
        case scenario1:
        case scenario2:
            return true;
        case scenario3:
            return false;
    }
}

inline bool isValue3(State s)
{
    // (This is silly too.  Do you really need this flag?)
    return isValue2(s);
}

inline bool isValue4(State s)
{
    switch (s)
    {
        case scenario1:
            return true;
        case scenario2:
        case scenario3:
            return false;
    }
}

Это определенно больше кода, чем в ответе Джана Паоло, но в зависимости от вашей ситуации это может быть гораздо более удобным:

  • Существует центральный набор функций для изменения, если добавляются дополнительные логические свойства или сценарии.
    • Добавление свойств требует добавления только одной функции.
    • При добавлении сценария включение предупреждений компилятора о необработанных случаях enum в операторах switch будет перехватывать свойства-получатели, которые не обрабатывают этот сценарий.
  • Если вам нужно динамически изменять логические свойства, вам не нужно повторно проверять их комбинации везде. Вместо переключения отдельных логических флагов (что может привести к недопустимым комбинациям флагов), вместо этого у вас будет конечный автомат, который переходит от одного сценария к другому.

Этот подход также имеет побочную выгоду, будучи очень эффективным.

Ответ 30

Несколько правильных ответов было дано на этот вопрос, но я бы сделал другое мнение: если код выглядит слишком сложным, что-то не совсем правильно. Код будет трудно отлаживать и, скорее всего, будет "одноразовым".

В реальной жизни, когда мы находим такую ситуацию:

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

Когда четыре состояния связаны такой точной схемой, мы имеем дело с конфигурацией некоторого "сущности" в нашей модели.

Крайняя метафора заключается в том, как мы будем описывать "человеческие существа" в модели, если бы мы не знали о их существовании как унитарных сущностях с компонентами, связанными с определенными степенями свободы: нам пришлось бы описывать независимые состояния "торсов", "руки", "ноги" и "голова", что затрудняет понимание описанной системы. Непосредственным результатом были бы неестественно сложные булевы выражения.

Очевидно, что способ уменьшить сложность - это абстракция, а инструментом выбора в c++ является парадигма объекта.

Поэтому возникает вопрос: почему существует такая картина? Что это и что представляет?

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

                0   1   2   3
Scenario 1:     T   T   T   T
Scenario 2:     T   T   T   F
Scenario 3:     T   F   F   F

В этот момент у вас есть начальная конфигурация. как массив. Например, std::array имеет оператор равенства:

В этот момент ваш синтаксис будет выглядеть следующим образом:

if( myarray == scenario1 ) {
  // arrays contents are the same

} 
else if ( myarray == scenario2 ) {
  // arrays contents are the same

} 

else if ( myarray == scenario3 ) {
  // arrays contents are the same

} 
else {
  // not the same

}

Как и ответ Джан Паоло, он короткий, понятный и легко проверяемый/отлаживаемый. В этом случае мы делегировали детали булевых выражений компилятору.