Как улучшить логику, чтобы проверить, соответствуют ли 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 голосов
/ 03 декабря 2018

Я бы стремился к удобочитаемости: у вас есть только 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 при работе с if.

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

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

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

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

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

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

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

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

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

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

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

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

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

Этот подход также имеет побочное преимущество:очень эффективно.

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

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

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

         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

}

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

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

простой

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

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

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;
}
0 голосов
/ 04 декабря 2018

использовать битовое поле :

unoin {
  struct {
    bool b1: 1;
    bool b2: 1;
    bool b3: 1;
    bool b4: 1;
  } b;
  int i;
} u;

// set:
u.b.b1=true;
...

// test
if (u.i == 0x0f) {...}
if (u.i == 0x0e) {...}
if (u.i == 0x08) {...}

PS :

Это очень жаль CPPers '.Но, UB не мое беспокойство, проверьте это на http://coliru.stacked -crooked.com / a / 2b556abfc28574a1 .

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

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

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

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

Моя идея состоит в том, чтобы преобразовать 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 bools в качестве ввода.замена 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
    }
}
0 голосов
/ 03 декабря 2018

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

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

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

AC / C ++ way

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

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

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