Если условие A соответствует, условие B должно соответствовать, чтобы выполнить действие C


148

Мой вопрос:

if (/* condition A */)
{
    if(/* condition B */)
      {
         /* do action C */
      }
    else
      /* ... */
}
else
{
   /* do action C */
}

Можно ли просто написать код действия C один раз вместо двух?

Как это упростить?


56
Поместите код для "действия C" в функцию?
CinCout

26
Это печально , это на самом деле не связано с C ++ вопрос должен HNQ: /
СМУ

2
Спасибо всем за помощь! В начале я просто хочу убедиться, что все в порядке, поэтому я использовал вложенный if. Это потому, что это самый простой способ, как я догадался. Я постараюсь приложить больше усилий, задавая вопросы в следующий раз. Желаю всем хорошего дня :)
starf15h

13
Это очень хорошая стратегия: сначала напиши код, а потом сделай его элегантным и эффективным.
Ученик

3
@ Я отправил это как ответ. Кстати, грустно видеть там меньше голосов.
CinCout

Ответы:


400

Ваш первый шаг в решении подобных проблем - создание логической таблицы.

A | B | Result
-------------------
T | T | do action C
T | F | ...
F | T | do action C
F | F | do action C

Как только вы сделали таблицу, решение станет ясным.

if (A && !B) {
  ...
}
else {
  do action C
}

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


35
Мне очень нравится, что вы показали таблицу истинности, чтобы помочь ОП понять, как разработать это самому. Можете ли вы сделать этот шаг дальше и объяснить, как вы получаете логическое выражение из таблицы истинности? Для кого-то новичка в программировании и логической логике это, вероятно, не совсем понятно.
Код-ученик

14
Если оценка Bимеет побочные эффекты, логическая таблица должна это учитывать.
Якк - Адам Невраумонт

79
@ Якк Мой ответ не касается побочных эффектов по двум причинам. Во-первых, решение (по совпадению) имеет правильное поведение побочных эффектов. Во-вторых, и что более важно, A и B, имеющие побочные эффекты, были бы плохим кодом, и обсуждение этого дополнительного случая отвлекло бы внимание на вопрос о булевой логике.
Вопрос

52
Возможно, стоит отметить, что в случае, когда A && !Bдело не в опере: !(A && !B)это эквивалентно тому, !A || Bчто вы можете сделать if (!A || B) { /* do action C */ }и избежать пустого блока.
KRyan

54
Если if (A && !B)будущим программистам действительно трудно поддерживать, то им действительно нет никакой помощи.
Рэй

65

У вас есть два варианта:

  1. Напишите функцию, которая выполняет «действие C».

  2. Переставьте свою логику, чтобы у вас не было так много вложенных операторов if. Спросите себя, какие условия вызывают «действие С». Мне кажется, что это происходит, когда «условие В» истинно или «условие А» ложно. Мы можем написать это как «НЕ А ИЛИ Б». Переводя это в C-код, мы получаем

    if (!A || B) {
        action C
    } else {
        ...
    }
    

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

Вы также должны узнать о «оценке короткого замыкания». Поэтому порядок выражений важен для точного дублирования вашей исходной логики. Хотя B || !Aлогически эквивалентно, использование этого в качестве условия будет выполнять «действие C», когда Bоно истинно, независимо от значения A.


15
@Yakk Смотрите законы де Моргана.
Ученик

@ Code-Apprentice Пожалуйста, прости мое плохое логическое мышление. Я хотел бы спросить, есть ли разница между (! A || B) и (A &&! B). Кажется, что оба в порядке для моей проблемы. Я имею в виду ваш и вопросC подход.
starf15h

6
@ Starf15h Есть еще одно важное отличие: где выполняется «действие C». Это различие делает наши два решения абсолютно эквивалентными. Я предлагаю вам воспользоваться "законами де Моргана", которые должны помочь вам понять, что здесь происходит.
Ученик по

5
Эти два решения в точности эквивалентны, но в зависимости от того, что именно, ...есть практическое различие . Если это вообще ничего (т. Е. «Делай С, если эти условия выполняются; в противном случае ничего не делай»), тогда это явно лучшее решение, так как тогда это elseутверждение можно просто пропустить.
Янус Бах Жакет

1
Кроме того, в зависимости от имен A и B, эта схема может быть более читаемой или менее читаемой для человека, чем схема QuestionC.
Майкл - Где Клэй Ширки

15

Вы можете упростить утверждение следующим образом:

if ((A && B) || (!A)) // or simplified to (!A || B) as suggested in comments
{
    do C
}

В противном случае поместите код для «C» в отдельную функцию и вызовите его:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}

7
Или прощеif (!A || B)
Tas

2
По логике, ((A && B) ||! A) эквивалентно (B ||! A)
Ученик по

@ Code-Apprentice B || !Aбудет работать trueтолько в том случае , если Bесть true, без фактической проверки на наличие Aкороткого замыкания
CinCout

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

1
Так что с логической точки зрения порядок не имеет значения. Однако, с точки зрения обслуживания и читабельности, может быть огромная разница в зависимости от того, что именно Aи что Bозначает.
Код-ученик

14

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

match (a,b) with
| (true,false) -> ...
| _ -> action c

Если вы не знакомы с синтаксисом, каждый шаблон представлен знаком | за ними следуют значения, совпадающие с (a, b), и подчеркивание используется в качестве подстановочного знака для обозначения «любых других значений». Поскольку единственный случай, когда мы хотим сделать что-то кроме действия c, это когда a истинно, а b ложно, мы явно указываем эти значения как первый шаблон (истина, ложь) и затем делаем все, что должно быть сделано в этом случае. Во всех остальных случаях мы переходим к шаблону «подстановочный знак» и выполняем действие c.


10

Постановка проблемы:

Если условие A соответствует, условие B должно соответствовать, чтобы выполнить действие C

описывает импликацию : A подразумевает B , логическое утверждение, эквивалентное !A || B(как упомянуто в других ответах):

bool implies(bool p, bool q) { return !p || q; }

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}

Возможно, отметьте это inlineдля C, constexprа также для C ++?
einpoklum

@einpoklum Я не стал вдаваться в некоторые из этих подробностей, потому что этот вопрос на самом деле не определил язык (но привел пример с C-подобным синтаксисом), поэтому я дал ответ с C-подобным синтаксисом. Лично я бы использовал макрос, чтобы условие B не оценивалось без необходимости.
Джеймсдлин

6

Тьфу, это тоже сбило меня с толку , но, как указывает Code-Apprentice, нам гарантированно понадобится do action Cили запустит вложенный elseблок, поэтому код можно упростить до:

if (not condition A or condition B) {
    do action C
} else {
    ...
}

Вот как мы попали в 3 случая:

  1. Вложенная do action Cв логику вашего вопроса требуется condition Aи condition Bдолжна быть ... trueВ этой логике, если мы достигнем 2- го слагаемого в ifутверждении, тогда мы знаем, что condition A, trueтаким образом, все, что нам нужно оценить, condition Bэтоtrue
  2. Вложенный else-блок в логике вашего вопроса должен condition Aбыть trueи condition Bдолжен быть false- Единственный способ, которым мы можем достичь else-блока в этой логике, был бы, если бы condition Aбыл trueи condition Bбылfalse
  3. Внешний else-блок в логике вашего вопроса должен condition Aбыть false- В этой логике, если condition Aложь, мы такжеdo action C

Реквизиты Code-Apprentice за то, что выправили меня здесь. Я бы предложил принять его ответ , так как он представил его правильно, без редактирования: /


2
Обратите внимание, что «условие А» не нужно оценивать снова. В C ++ мы имеем закон исключенного среднего. Если «не условие А» ложно, то «условие А» обязательно верно.
Код-ученик

1
Из-за оценки короткого замыкания, Bбудет оцениваться, только если !Aложно. Таким образом, оба должны потерпеть неудачу для выполнения elseоператоров.
Код-ученик

Даже без короткого замыкания оценка !A || Bложна именно тогда, когда оба !Aи Bложны. Следовательно, Aбудет верно, когда elseисполняется. Не нужно переоценивать A.
Код-ученик

@ Code-Apprentice Хорошо воняйте, отличное наблюдение, я исправил свой ответ, но предложил принять ваш. Я просто пытаюсь объяснить, что вы уже выдвинули.
Джонатан Ми

Я хотел бы дать вам еще один голос за объяснение каждого случая.
Код-ученик

6

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

f = ab +! a
f =?

Как доказанная проблема, это приводит к f = !a + b. Есть несколько способов доказать эту проблему, такие как таблица истинности, карта Карно и так далее.

Так что в языках на основе C вы можете использовать следующее:

if(!a || b)
{
   // Do action C
}

PS: Карта Карно также используется для более сложной серии условий. Это метод упрощения выражений булевой алгебры.


6

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

Первое, что вы хотите сделать, это посмотреть, при каких условиях вы хотите выполнить C. Это тот случай, когда (a & b). Также когда !a. Так и есть (a & b) | !a.

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

(a & b) | !a = (a | !a) & (b | !a), а | ! А всегда верно, так что вы можете просто пересечь его, который оставляет вас с свернутым результатом: b | !a. В случае, если порядок имеет значение, потому что вы хотите проверить b, только если! A истинно (например, когда! A является проверкой нулевого указателя, а b является операцией над указателем, как @LordFarquaad указал в его комментарии), вы можете хочу поменять два.

Другой случай (/ * ... * /) всегда будет выполняться, когда c не выполняется, поэтому мы можем просто поместить его в другой случай.

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

Что оставляет нас со следующим кодом:

if (!A || B)
{
    doActionC()  // execute method which does action C
}
else
{
   /* ... */ // what ever happens here, you might want to put it into a method, too.
}

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


4

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

bool do_action_C;

// Determine whether we need to do action C or just do the "..." action
// If condition A is matched, condition B needs to be matched in order to do action C
if (/* condition A */)
{
    if(/* condition B */)
      do_action_C = true; // have to do action C because blah
    else
      do_action_C = false; // no need to do action C because blarg
}
else
{
  do_action_C = true; // A is false, so obviously have to do action C
}

if (do_action_C)
  {
     DoActionC(); // call the function
  }
else
  {
  ...
  }


2

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

Извлечь C:

if (A) {
   if (B)
      C();
   else
      D();
} else
   C();

Обрати сначала, ifчтобы избавиться от первого else:

if (!A) {
   C();
   return;
}

if (B)
   C();
else
   D();

Избавьтесь от второго else:

if (!A) {
   C();
   return;
}

if (B) {
   C();
   return;
} 

D();

И тогда вы можете заметить, что два случая имеют одинаковое тело и могут быть объединены:

if (!A || B) {
   C();
   return;
}

D();

Необязательные вещи для улучшения будут:

  • зависит от контекста, но если !A || Bсбивает с толку, извлеките его в одну или несколько переменных, чтобы объяснить намерение

  • какой бы из C()или D()не был исключительный случай должен идти последним, так что если D()это исключение, то инвертировать ifпоследний раз


2

Использование флагов также может решить эту проблему

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.