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

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

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

Я бы использовал таблицу вместо сложного оператора if. Кроме того, поскольку это логические флаги, вы можете моделировать каждый сценарий как константу и проверять его.

Zdeslav Vojkovic 03.12.2018 11:16
if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
mch 03.12.2018 11:17

Вы можете использовать symPy: docs.sympy.org/latest/modules/logic.html

Oliv 03.12.2018 11:24

каковы сценарии на самом деле? Часто все становится намного проще, если вы просто даете вещам собственные имена, например bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;.

463035818_is_not_a_number 03.12.2018 11:57

@ user463035818 Технически они представляют недели. Только первая неделя должна быть истинной. На второй неделе все 4 значения должны быть верными. Третья, четвертая и пятая недели должны быть верными только первые 3 недели.

Andrew Truckle 03.12.2018 12:06

Другой вариант: компилировать с GCC. Если вам посчастливилось скомпилировать с помощью gcc, позвольте ему сделать упрощение. Другие компиляторы не имеют такой возможности: godbolt.org/z/OJ8z7N

Oliv 03.12.2018 12:11

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

Hardik Modha 03.12.2018 13:29

Лучшее, что я могу сделать вручную - это bValue1 && (bValue2 == bValue3) && (bValue4<=bValue2). Это 4 оператора.

MSalters 03.12.2018 16:40

используйте переключатель / case: вы можете преобразовать все bool в целые 0 и 1. затем сдвинуть каждый bool на один бит. затем используйте переключатель / чехол для 3 сценариев. иногда переключатель / корпус более читабельны, но ИМО, сдвигающая часть делает код немного сложнее для среднего программиста по обслуживанию.

Trevor Boyd Smith 03.12.2018 21:42

Это больше похоже на задание для перечисления, которое явно перечисляет набор возможных значений (с семантически значимыми именами), возможно, с конструктором, который обеспечивает правильный регистр для данного набора логических значений. Несмотря на то, что у вас есть 4 логических значения, у вас есть только 2 бита информации (состояние 1, состояние 2, состояние 3, недопустимое состояние)

Alexander 03.12.2018 21:45

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

Cort Ammon 03.12.2018 22:19

@CortAmmon Это действительно немного не по теме. С точки зрения программирования это можно считать «перечислением». В реальной жизни данные представляют собой различные «сценарии» того, как предметы учащегося расставлены на собрании.

Andrew Truckle 03.12.2018 22:50

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

usr 04.12.2018 13:37

Множество разных ответов, сводящихся к расплывчатому вопросу "Можно ли улучшить / упростить логику этого утверждения?" Любой код подобен треугольнику «два выбора» между читаемостью / ремонтопригодностью, скоростью выполнения и размером кода / временем компиляции. Это приводит к тому, что пользователи говорят, что вы можете исключить ABC + AB'C'D' = A(BC + B'C'D'), чтобы немного повысить скорость, но при этом ухудшить читаемость. В дальнейшем уточняйте, какие из них имеют приоритет.

Wolfzoon 04.12.2018 14:58

Обратите внимание, что bvalue4 на самом деле не имеет значения: случаи №1 и №2 означают, что вы можете отказаться от bvalue4 без каких-либо промахов.

ADS 05.12.2018 07:36

@ADS Ага. Я просто сохраняю его, чтобы отразить дословный график недельных сценариев.

Andrew Truckle 05.12.2018 07:51

FYI en.wikipedia.org/wiki/Karnaugh_map

00__00__00 05.12.2018 15:43

Для меня это станет классическим вопросом SO. Это яркий пример любви разработчиков к смекалке и сложности. Без других ограничений большинство предпочтительных ответов ужасно оцениваются по моей шкале читаемости. Когда вы достаточно долго кодируете, вы в конце концов понимаете, что, по крайней мере, в 99% случаев удобочитаемость - это, безусловно, самая важная вещь, к которой нужно стремиться. Исчезли глубоко вложенные IF, вариативные шаблоны, карты Карно, побитовые операции и отсутствие самодокументирования.

D'Arcy Rittich 06.12.2018 14:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
119
18
18 932
30
Перейти к ответу Данный вопрос помечен как решенный

Ответы 30

Как предлагает mch, вы можете:

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

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

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

Ответ принят как подходящий

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

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 значения верны, я могу избежать проверки четвертого значения»: это затруднит чтение и сопровождение вашего кода.

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

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

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

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

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

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

@gsmaras Обычно я ненавижу последовательность if, else if, but if, whatAboutThisIf и т. д. Но в данном конкретном случае это, кажется, (а?) Путь. Я поддержал ответ Зладека, может быть, труднее читать, но очень легко поддерживать

Gian Paolo 03.12.2018 11:44

Единственным недостатком является масштабируемость ИМХО, что, если количество допустимых условий изменится позже до 10 или около того

hessam hedieh 03.12.2018 11:44

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

Gian Paolo 03.12.2018 11:49

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

Andrew Truckle 03.12.2018 12:01

Это можно еще больше упростить, поместив все условия в инициализатор для valid и разделив их с помощью ||, вместо того, чтобы изменять valid в отдельных блоках операторов. Я не могу привести пример в комментарии, но вы можете вертикально выровнять операторы || по левому краю, чтобы сделать это очень ясным; отдельные условия уже заключены в круглые скобки столько, сколько они должны быть (для if), поэтому вам не нужно добавлять какие-либо символы в выражения, кроме того, что уже есть.

Leushenko 03.12.2018 12:13

@Leushenko, я думаю, что круглые скобки, && и || Условия довольно подвержены ошибкам (кто-то в другом ответе сказал, что в коде в OP была ошибка в скобках, возможно, она была исправлена). Конечно, может помочь правильное выравнивание. Но в чем преимущество? читабельнее? легче поддерживать? Я так не думаю. Просто мое мнение, конечно. Будьте уверены, я действительно ненавижу много "если" в коде.

Gian Paolo 03.12.2018 13:43

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

Gian Paolo 03.12.2018 14:52

@GianPaolo, спасибо за ответ (проголосовало за :)), если бы это был я, я бы пошел на второй подход, я также опубликовал ответ в более C-ish стиле. посмотри здесь, если тебе нравится

hessam hedieh 03.12.2018 14:59

Я бы обернул его в if ($bValue1), так как это всегда должно быть правдой, технически допуская небольшое улучшение производительности (хотя здесь мы говорим о ничтожных количествах).

Martijn 03.12.2018 15:36

@Martijn: Не стоит недооценивать компиляторы. Если повезет, они также могут определить общее состояние, которое вызывает bValue2==bValue3, поэтому последующие 3 проверки необходимы только для тестирования bValue2.

MSalters 03.12.2018 16:32

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

Gian Paolo 03.12.2018 16:35

FWIW: есть только 2 сценария: первые 2 являются одним и тем же сценарием и не зависят от bValue4

Dancrumb 03.12.2018 20:48

Я в целом согласен с этим ответом, но все же считаю, что должно быть только одно назначение (фактически, одно инициализация), и что valid должен быть объявлен как const. Конечно, это возможно с использованием условного оператора, а не операторов if. Это не только улучшает правильность констант, но и улучшает читаемость.

Konrad Rudolph 03.12.2018 23:24

Мне это кажется очень нечитаемым, а главное отличие - маленькие восклицательные знаки.

shogged 04.12.2018 12:53

Почему бы не использовать здесь логическое И вместо логического И? Он будет таким же с точки зрения удобочитаемости, но исключит ветвления, вызванные операциями логического И.

darnir 04.12.2018 14:14

@shogged Я тоже подумал, что это хорошее место для использования Ключевое слово not вместо почти невидимого !.

Ruslan 05.12.2018 15:28

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

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

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

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

Adham Zahran 03.12.2018 11:35

ИМО, таблица - лучший подход, поскольку она лучше масштабируется с дополнительными сценариями и флагами.

Zdeslav Vojkovic 03.12.2018 11:44

Мне нравится ваше первое решение, оно легко читается и может быть изменено. Я бы сделал 2 улучшения: 1: присвоить значения сценарию X с явным указанием используемых логических значений, например. SCENARIO_2 = true << 3 | true << 2 | true << 1 | false; 2: избегайте переменных SCENARIO_X, а затем сохраните все доступные сценарии в <std::set<int>. Добавление сценария будет чем-то вроде mySet.insert( true << 3 | false << 2 | true << 1 | false;, возможно, излишним для всего 3 сценария, OP принял быстрое, грязное и простое решение, которое я предложил в своем ответе.

Gian Paolo 03.12.2018 13:52

Если вы используете C++ 14 или выше, я бы предложил вместо этого использовать двоичные литералы для первого решения - 0b1111, 0b1110 и 0b1000 намного понятнее. Вы, вероятно, также можете немного упростить это, используя стандартную библиотеку (std::find?).

Bernhard Barker 03.12.2018 15:25

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

Konrad Rudolph 03.12.2018 23:25

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

Это дает:

Меняя 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, является хорошей практикой.

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

Zdeslav Vojkovic 03.12.2018 12:00

@ZdeslavVojkovic: Я бы просто добавил комментарий к уравнению. //!(ABC + AB'C'D') (By K-Map logic). Для разработчика было бы хорошее время изучить K-Maps, если он еще не знает их.

P.W 03.12.2018 12:05

Я согласен с этим, но проблема в том, что проблема в том, что IMO не четко отображает проблемную область, то есть как каждое условие сопоставляется с конкретным сценарием, что затрудняет изменение / расширение. Что происходит, когда есть условия E и F и 4 новых сценария? Сколько времени нужно, чтобы правильно обновить эту инструкцию if? Как проверка кода проверяет, все ли в порядке? Проблема не в технической стороне, а в «деловой» стороне.

Zdeslav Vojkovic 03.12.2018 12:09

Я думаю, вы можете исключить A: ABC + AB'C'D' = A(BC + B'C'D') (это можно даже разложить на A(B ^ C)'(C + D'), хотя я бы осторожно назвал это «упрощением»).

Maciej Piechotka 03.12.2018 12:31

@ P.W Этот комментарий кажется таким же понятным, как и код, и поэтому немного бессмысленен. Лучший комментарий объяснил бы, как вы на самом деле пришли к этому уравнению, то есть что оператор должен запускаться для TTTT, TTTF и TFFF. В этот момент вы могли бы просто написать эти три условия в коде и вообще не нуждаться в объяснении.

Bernhard Barker 03.12.2018 15:21

@ P.W Или, лучше, // Simplified by Karnaugh map; must match TTTT, TTTF and TFFF, который дает имя полный для облегчения поиска в Google, показывает предполагаемую логику и не пропускает никаких шагов в вычислении (потому что он их не показывает).

Nic 03.12.2018 18:36

На самом деле я узнал об этом в университете и не думал, что люди на самом деле это используют: O

Dhruv Prakash 03.12.2018 20:58

Я бы добавил несколько лишних скобок. Многие разработчики не совсем усвоили приоритет логических операторов (&& vs ||) в той же степени, что и арифметические операторы, поэтому круглые скобки не позволят читателям усомниться.

BenM 03.12.2018 22:17

Ранее я написал ответ, описывающий, как использовать карту Карно, который может быть полезной сноской к этому ответу. electronics.stackexchange.com/a/298508/145753

Vaelus 04.12.2018 04:14

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

cole 04.12.2018 05:43

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

Sneftel 04.12.2018 16:58

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

philfr 05.12.2018 07:59

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

Red.Wave 05.12.2018 19:21

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

DrMcCleod 06.12.2018 13:00

Разве компилятор не должен этого делать?

Boris 02.01.2019 04:02

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

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

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


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

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

Да, но трудно понять. Но спасибо за предложение.

Andrew Truckle 03.12.2018 11:58

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

Oliv 03.12.2018 12:07

@AndrewTruckle: обратите внимание, что если вам нужна более читаемая версия, скажите об этом. Вы сказали «упрощенный», но соглашаетесь с еще более подробной версией, чем исходная.

geza 03.12.2018 12:12

@Oliv: спасибо за этот тест! Это доказывает, что, хотя компиляторы становятся все лучше и лучше, им еще есть чему поучиться :)

geza 03.12.2018 12:15
simple - действительно расплывчатый термин. Многие люди понимают это в этом контексте как более простое для понимания разработчиком, а не компилятором для генерации кода, поэтому более подробное может быть проще.
Zdeslav Vojkovic 03.12.2018 12:16

@ZdeslavVojkovic: "упрощенная логика высказываний". Это совсем не расплывчато. Это означает более простое выражение с меньшим количеством терминов.

geza 03.12.2018 12:18

Это совсем нетрудно понять, хотя я бы лично поменял местами второй и третий члены, чтобы дать bValue1 && (bValue2||!bValue4) && (bValue2==bValue3), так что абсолютное ограничение на bValue2 стоит перед относительным.

Jack Aidley 03.12.2018 15:27

Я считаю, что запутывающая деталь - это последнее условие. Я попытался упростить и пришел к этому решению, за исключением того, что я использовал обычный / третий (?) Оператор (?:) для последнего шага, который субъективно более читабелен. Добавление объяснения того, как все работает, вероятно, лучше всего.

Ismael Miguel 04.12.2018 19:45

@IsmaelMiguel: когда логическая формула оптимизируется по количеству терминов, исходное значение обычно теряется. Но вокруг него можно поставить комментарий, чтобы было понятно, что он делает. Даже для принятого ответа комментарий не повредит.

geza 04.12.2018 19:53

Просто примечание, чтобы сказать, что этот ответ представляет собой способ C++ написать то же выражение в комментарии @Maciej Piechotka к ответу P.W на этот вопрос.

Happy Green Kid Naps 05.12.2018 23:00

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

12Me21 07.12.2018 16:26

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

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

Хотя я почти уверен, что это неправильно. Предполагается, что компилятор использует только одно двоичное представление для true. Компилятор, который использует «все, что ненулевое, истинно», приводит к сбою этого кода. Обратите внимание, что true должен быть конвертировать на 1, просто он не обязательно должен быть хранится как таковой.

MSalters 03.12.2018 16:43

@MSalters, tnx, я понимаю вашу точку зрения, и я знаю, что, вроде 2 is not equal to true but evaluates to true, мой код не заставляет int 1 = true и работает до тех пор, пока все true преобразуются в одно и то же значение int, поэтому вот мой вопрос: почему компилятор должен действовать случайным образом о преобразовании true в базовый int, не могли бы вы подробнее рассказать?

hessam hedieh 03.12.2018 17:01

Выполнение memcmp для проверки логических условий - это нет в стиле C++, и я также очень сомневаюсь, что это уже установленный способ C.

Konrad Rudolph 03.12.2018 23:27

@hessamhedieh: Проблема в вашей логике "преобразование true в базовый int". Так работают компиляторы нет,

MSalters 04.12.2018 11:25

Ваш код увеличивает сложность с O (1) до O (n). Ни на одном языке не выходить - оставьте в стороне C / C++.

mesibo 05.12.2018 05:42

Я обозначаю 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))

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

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

Моя идея состоит в том, чтобы преобразовать 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
    }
}

Мне нравится этот подход, за исключением названия основной функции: «from bool… to Какие?» - Почему не явно, bitmap_from_bools или bools_to_bitmap?

Konrad Rudolph 03.12.2018 23:29

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

Stack Danny 04.12.2018 08:57

Думаю, тебе нужен summary!= 0b1111u &&.... a != b || a != c всегда верно, если b != c

MooseBoys 05.12.2018 16:38

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

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

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

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

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

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

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

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

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

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

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

В какой-то момент многословие начинает ухудшать удобочитаемость. Я думаю, это заходит слишком далеко.

JollyJoker 03.12.2018 14:01

@JollyJoker Я действительно согласен с этой конкретной ситуацией - однако, мое внутреннее ощущение от того, как OP назвал все в очень общем виде, заключается в том, что их «настоящий» код, вероятно, намного сложнее, чем приведенный ими пример. На самом деле, я просто хотел представить эту альтернативу, так как я бы структурировал ее для чего-то гораздо более сложного / запутанного. Но вы правы - для конкретного примера OP это слишком многословно и усугубляет ситуацию.

user955340 03.12.2018 14:30

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

В итоге я решил добавить три новых "сценарных" метода 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 значения.

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

Спасибо, что поделились решением. :) На самом деле это лучше, чем сложные адские условия. Может быть, вы все еще можете назвать INCLUDE_ITEM1 и т. д. Лучше, и у вас все в порядке. :)

Hardik Modha 03.12.2018 14:04

@HardikModha Ну, технически это «предметы для учащихся», и флаг должен указывать, должны ли они быть «включены». Так что я думаю, что название, хотя и звучит обобщенно, на самом деле имеет значение в этом контексте. :)

Andrew Truckle 03.12.2018 14:06

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

Начиная с ответа @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, даже если он не изящный и не обслуживаемый.

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

Andreas 19.02.2020 07:08

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

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

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

по многочисленным просьбам:

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

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

jxh 03.12.2018 23:45

@jxh Это числа, используемые OP. Я только что удалил bValue.

ispiro 04.12.2018 00:26

@jxh Надеюсь, теперь стало лучше.

ispiro 05.12.2018 08:49

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

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

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

Dirk Herrmann 04.12.2018 00:53

Каждый ответ слишком сложен и труден для чтения. Лучшее решение - заявление 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 для еще большей читабельности.

Будучи старым программистом на C, я бы определил макрос «PackBools» и использовал бы его как для «switch (PackBools (a, b, c, d))», так и для случаев, например, либо напрямую »case PackBools (true , true ...) "или определите их как локальные константы. например "const беззнаковый int сценарий1 = PackBools (истина, истина ...);"

Simon F 06.12.2018 15:17

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

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

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

+1 Я бы тоже так поступил. Как указывает @RedFilter, и в отличие от принятого ответа, это самодокументируется. Присвоение сценариям собственных имен на отдельном шаге гораздо удобнее для чтения.

Andreas 05.12.2018 14:07

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

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:

Это большая жалость для CPP. Но UB меня не беспокоит, проверьте это на http://coliru.stacked-crooked.com/a/2b556abfc28574a1.

Это вызывает UB из-за доступа к неактивному полю объединения.

HolyBlackCat 04.12.2018 14:44

Формально это UB в C++, вы не можете установить один член объединения и читать из другого. Технически было бы лучше реализовать шаблонные геттеры \ сеттеры для битов целого значения.

Swift - Friday Pie 04.12.2018 14:45

Я думаю, что поведение изменилось бы на «Определенное реализацией», если бы адрес объединения был преобразован в unsigned char*, хотя я думаю, что простое использование чего-то вроде ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1, вероятно, было бы более эффективным.

supercat 04.12.2018 20:35

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

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

Andrew Truckle 05.12.2018 06:30

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

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

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

Первую версию так легко читать и поддерживать, что она мне очень нравится. Второй труднее читать, по крайней мере, для меня, и для него требуется уровень навыков C++, может быть, выше среднего, наверняка выше моего. Не все умеют писать. Только что узнал что-то новое, спасибо

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

просто

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

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

Поразрядный сравнение мне кажется читаемым. Композиция же выглядит искусственно.

xtofl 05.12.2018 08:25

Некоторым людям будет легче читать вложенные 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;
}

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

Dnomyar96 06.12.2018 11:57

@ Dnomyar96 согласен. Лично я тоже избегаю вложенных «если». Иногда, если логика сложна, мне легче понять логику, разбив ее на части. Например, когда вы входите в блок bValue1, вы можете рассматривать все в нем как новую свежую страницу в своем умственном процессе. Готов поспорить, подход к проблеме может быть очень личным или даже культурным.

sardok 06.12.2018 12:14

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

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

Проверьте sum на соответствие условиям, которые вам нужны, и все.

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

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

The acceptable values are:

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

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

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

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

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

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

         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

}

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

Принятый ответ хорош, если у вас всего 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))

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

if (!bValue1)
    return false;
if (bValue2 != bValue3)
    return false;
if (bValue3 == false && bValuer4 == true)
    return false;
return true;

Другие вопросы по теме