150

My question is:

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

Is it possible to just write the code of action C one time instead of twice?

How to simplify it?

Siyavash Hamdi
  • 2,764
  • 2
  • 21
  • 32
starf15h
  • 1,469
  • 2
  • 9
  • 12

12 Answers12

401

Your first step in these kinds of problems is always to make a logic table.

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

Once you've made the table, the solution is clear.

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

Do note that this logic, while shorter, may be difficult for future programmers to maintain.

QuestionC
  • 10,006
  • 4
  • 26
  • 44
  • 35
    I really like that you showed the truth table to help the OP understand how to develop this himself. Can you take this a step further and explain how you get the boolean expression from the truth table? For someone new to programming and boolean logic, this is probably not at all clear. – Code-Apprentice Jul 20 '17 at 03:43
  • 14
    If evaluating `B` has side effects the logic table has to account for that. – Yakk - Adam Nevraumont Jul 20 '17 at 03:49
  • 79
    @Yakk My answer doesn't address side-effects for two reasons. First, the solution does (coincidentally) have the correct side effect behavior. Secondly, and more importantly, A and B having side effects would be bad code and a discussion about that fringe case would be a distraction for a question fundamentally about boolean logic. – QuestionC Jul 20 '17 at 03:57
  • 7
    I don't see how this would be any more difficult to maintain than the original code. If anything, it's easier *because* it's shorter. Even if you have to make a truth table when faced with unfamiliar conditional expressions, the conciseness means you aren't repeating yourself (DRY). – Cody Gray - on strike Jul 20 '17 at 09:56
  • 3
    @CodyGray Depending on the naming of A and B, and their purpose, the code might be confusing because it does not follow the human way to think about it and merges cases that might seem not the same to a human. – lucidbrot Jul 20 '17 at 12:33
  • 2
    In which case, rename things so it does make sense. Use your refactoring tools properly! – Draco18s no longer trusts SE Jul 20 '17 at 13:22
  • 52
    Perhaps worth noting, in case the `A && !B` case is a no-op: `!(A && !B)` is equivalent to `!A || B` which means you can do `if (!A || B) { /* do action C */ }` and avoid an empty block. – KRyan Jul 20 '17 at 17:32
  • 55
    If `if (A && !B)` is truly difficult for future programmers to maintain, then there's really no helping them. – Ray Jul 21 '17 at 00:32
  • 2
    Thanks for the logic table idea. I always struggle with hard conditions like this, guess this will help in the future – Mario Garcia Jul 21 '17 at 06:03
  • 6
    @KRyan For completeness I would add that you used De Morgan's laws to do that conversion (may be helpful to understand how boolean expressions of that form can be converted in general). https://en.wikipedia.org/wiki/De_Morgan%27s_laws – Dennis Jul 21 '17 at 10:20
  • @CodyGray: It's not that anyone's saying you *aren't* a human, but [on the internet, know one really knows for sure](https://en.wikipedia.org/wiki/File:Internet_dog.jpg#/media/File:Internet_dog.jpg) ;) – tonysdg Jul 21 '17 at 13:57
  • 10
    Oh, c'mon, somebody at least mention [Karnaugh maps](https://en.wikipedia.org/wiki/Karnaugh_map). – shoover Jul 21 '17 at 15:24
  • @CodyGray Human or not, you are definitely not "Not a Robot" ;) https://stackoverflow.com/help/badges/6381/not-a-robot – CinCout Jul 21 '17 at 17:36
  • Wouldn't it be more clear to write: if (A && B) do C, else do something else? It follows more logically from the table imo. – Rivasa Jul 23 '17 at 16:20
  • @Annabelle: Are you arguing that whatever B is, it should be inverted (e.g. from `IsRedColor` to `IsNotRedColor`) so as to avoid the `!` in the evaluation? I **strongly** disagree. B's name (and the meaning behind its true/false state) should be decided intrinsically, not based on how you will use it in an evaluation further on. There is also no guarantee that you won't need a different evaluation in another part of the logic (OP's case: `if A && !B then C`, and in another method `if D && B then E`), therefore making it impossible to avoid the `!` operator in both cases anyway. – Flater Jul 24 '17 at 07:31
  • @Annabelle: Re-reading your comment, it seems you are arguing that `A && B` is the result from the table. That is not the case. If it were `A && B`, the resulting column would have to be `do C, nothing, nothing, nothing` – Flater Jul 24 '17 at 07:33
  • @Annabelle `A && B` is only one of the three cases where `C` should be done. – Baldrickk Jul 24 '17 at 09:36
  • @Flater to back up your disagreement with naming, variable names should always be positive. `a` and `!a` are easy to parse (as a human) while `not_a` may be easy enough too, `!not_a` begins to make it a mess. – Baldrickk Jul 24 '17 at 09:38
  • @Baldrickk: I agree, but that's more a matter of my simple example using a very simple inversion. A less obvious example would be `IsLefthanded` and `IsRighthanded`, or `IsDead` and `IsAlive`. Neither of these examples make it inherently clear that the options are limited to two options! (e.g. `IsAmbidextrous` or `IsGhost`). However, for my example, `IsRed` and `IsNotRed` **do** inherently show that there are only two possible options, which is why I chose that example to avoid unnecessary implications about more than two options existing. – Flater Jul 24 '17 at 09:47
  • @Baldrickk How so? It says in question: 'IF' condition A matches, condition B must ALSO match in order to execute action C. Wouldn't this mean that both A and B must be true? – Rivasa Jul 24 '17 at 13:12
  • 1
    @Annabelle There is also the other case, when `A` is not true, action `C` is also executed. It is only not performed when `A` is truthy and `B` is falsey. (you need to read the example code in the question, not just the question title) – Baldrickk Jul 24 '17 at 13:41
65

You have two options:

  1. Write a function that performs "action C".

  2. Rearrange your logic so that you do not have so many nested if statements. Ask yourself what conditions cause "action C" to occur. It looks to me like it happens when either "condition B" is true or "condition A" is false. We can write this as "NOT A OR B". Translating this into C code, we get

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

To learn more about these kind of expressions, I suggest googling "boolean algebra", "predicate logic", and "predicate calculus". These are deep mathematical topics. You don't need to learn it all, just the basics.

You should also learn about "short circuit evaluation". Because of this, the order of the expressions is important to exactly duplicate your original logic. While B || !A is logically equivalent, using this as the condition will execute "action C" when B is true regardless of the value of A.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • 15
    @Yakk See deMorgan's Laws. – Code-Apprentice Jul 20 '17 at 03:48
  • @Code-Apprentice Please forgive my bad logical thinking. I would like to ask if there is any difference between (!A || B) and (A && !B). It seems both are okay for my problem. I mean yours and QuestionC's approach. – starf15h Jul 20 '17 at 04:37
  • 6
    @Starf15h There is one other crucial difference: where the "action C" is executed. This difference makes our two solutions exactly equivalent. I suggest that you google "deMorgan's Laws" which should help you understand what is going on here. – Code-Apprentice Jul 20 '17 at 06:18
  • 5
    The two solutions are exactly equivalent, but there may be a practical difference _depending on what exactly `...` is_. If it is nothing at all (i.e., “do C if these conditions are met; otherwise do nothing”), then this is clearly the superior solution, since the `else` statement can simply be left out altogether then. – Janus Bahs Jacquet Jul 20 '17 at 11:24
  • 1
    Also, depending on the names of A and B, this arrangement may be more readable or less readable [_to a human_](https://stackoverflow.com/a/45204700/241211#comment77395622_45204700) than QuestionC's arrangement. – Michael Jul 20 '17 at 13:08
  • @LordFarquaad Similarly, `(!A || B)` and `(B || !A)` are logically equivalent, but only the former matches the behavior of the original `if` statement. – Code-Apprentice Jul 20 '17 at 16:14
  • @LordFarquaad Note that the question was originally tagged as [tag:c++] which does not have NPE. – Code-Apprentice Jul 20 '17 at 16:28
  • I assume that OP is learner programmer, so the best thing we should do is to teach him best practices from the very beginning: (my upvote for extraction to separate function). Moreover: code should have command and query separation, and if so, side effects of A and B should stop to be a problem, logical statements should be evaluated i.e. into a variable with meaningful name etc... reference: http://www.goodreads.com/book/show/3735293-clean-code – mpasko256 Jul 20 '17 at 17:59
  • @Code-Apprentice Congratulations for getting more votes even after posting a late answer :) – CinCout Jul 20 '17 at 19:02
  • @CinCout and the other even later answer got even more votes! – Code-Apprentice Jul 20 '17 at 20:11
  • @Code-Apprentice Well, at least he got something different to say! – CinCout Jul 20 '17 at 20:12
  • Given that `C` will execute whenever `B` is true, regardless of `A`, surely `if (B || !A)` is clearer? – BallpointBen Jul 21 '17 at 15:14
  • @BallpointBen Because `if(!A || B)` more closely follows the logic of the original nested `if...else`, I can argue that this is clearer. I think a lot depends on the exact nature of `A` and `B` and what makes the most sense in that context. – Code-Apprentice Jul 21 '17 at 15:53
  • I always find it clearer when the first condition is not negated, but maybe that's just me. – BallpointBen Jul 21 '17 at 15:54
  • @BallpointBen I can understand that this rule of thumb works in many, if not most, situations. However, I shy away from rules that start with "always" or "never". Such absolutes are too rigid. – Code-Apprentice Jul 21 '17 at 15:59
  • Well, sure. `if (!success) { fail(); return; }` is clearer than `if (success) { ... } else { fail() }`. I should have said "in general". – BallpointBen Jul 21 '17 at 16:00
  • @BallpointBen When the entire condition is negated and there are statements for both branches, I also prefer using the non-negated condition. That is not what we have here. – Code-Apprentice Jul 21 '17 at 16:01
15

You can simplify the statement like this:

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

Otherwise put code for 'C' in a separate function and call it:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
    {
        DoActionC(); // call the function
    }
    else
    ...
}
else
{
   DoActionC(); // call the function
}
Michael
  • 8,362
  • 6
  • 61
  • 88
CinCout
  • 9,486
  • 12
  • 49
  • 67
  • 7
    Or more simply `if (!A || B)` – Tas Jul 20 '17 at 03:22
  • 2
    Logically, ((A&&B) || !A) is equivalent to (B || !A) – Code-Apprentice Jul 20 '17 at 03:22
  • @Code-Apprentice `B || !A` will result to `true` only if `B` is `true`, without actually checking for `A` due to short-circuiting – CinCout Jul 20 '17 at 03:25
  • Instead a function , a lambda can be used, if you have a latest compiler. A lambda can capture the necessary variables instead of declaring each of them in the function case. – balki Jul 20 '17 at 03:26
  • 1
    @CinCout Good point. While my statement is still true from a theoretical boolean logic perspective, I did not take into account the practicalities of short circuit boolean operators. Fortunately, my own answer has the correct order. – Code-Apprentice Jul 20 '17 at 03:28
  • @CinCout, mhm what? With both `A` and `B` false, `B || !A` would first test `B`, then `!A` and return a true value. And `!A || B` would test (only) `!A` and return a true value. The difference matters only if the conditions have side-effects. (And in that case `(A && B) || (!A)` would be yet different.) – ilkkachu Jul 20 '17 at 10:40
  • @ilkkachu The problem occurs when `B` is true. Then `B || !A` evaluates to true without evaluating `!A`. This is not the same as the order of the logic in the original nested `if...else`....but now that I think about it more closely, I'm uncertain if it matters. If `B` is true and `A` is false, `B || !A` is true and "action C" is executed. This is fine because when `A` is false, the original code executes C. If `B` is true and `A` is true. Then likewise, C is executed which again agrees with the results of the original code. – Code-Apprentice Jul 20 '17 at 16:20
  • 1
    So from a logic perspective, the order does not matter. However, from a maintenance and readability point of view, there might be a huge difference depending on what exactly `A` and `B` stand for. – Code-Apprentice Jul 20 '17 at 16:21
  • @Code-Apprentice, yes. Short-circuit evaluation doesn't affect the logic (that would be silly and completely unusable). It only affects side-effects. – ilkkachu Jul 21 '17 at 13:05
  • @ilkkachu That's why we avoid side effects in condition evaluation. – Code-Apprentice Jul 21 '17 at 14:08
14

In a language with pattern matching, you can express the solution in a way that more directly reflects the truth-table in QuestionC's answer.

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

If you're not familiar with the syntax, each pattern is represented by a | followed by the values to match with (a,b), and the underscore is used as a wildcard to mean "any other values". Since the only case where we want to do something other than action c is when a is true and b is false, we explicitly state those values as the first pattern (true,false) and then do whatever should be done in that case. In all other cases, we fall through to the "wildcard" pattern and do action c.

Aaron M. Eshbach
  • 6,380
  • 12
  • 22
10

The problem statement:

If condition A is matched, condition B needs to be matched in order to do action C

describes implication: A implies B, a logical proposition equivalent to !A || B (as mentioned in other answers):

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

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • Perhaps mark it `inline` for C and `constexpr` as well for C++? – einpoklum Jul 22 '17 at 15:47
  • @einpoklum I didn't get into some of those details because this question didn't really specify a language (but gave an example with C-like syntax), so I gave an answer with C-like syntax. Personally I would use a macro so that condition B isn't evaluated unnecessarily. – jamesdlin Jul 22 '17 at 19:39
6

Ugh, this tripped me up too, but as pointed out by Code-Apprentice we're guaranteed to need to do action C or run the nested-else block, thus the code could be simplified to:

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

This is how we're hitting the 3 cases:

  1. The nested do action C in your question's logic required condition A and condition B to be true -- In this logic, if we reach the 2nd term in the if-statement then we know that condition A is true thus all we need to evaluate is that condition B is true
  2. The nested else-block in your question's logic required condition A to be true and condition B to be false -- The only way that we can reach the else-block in this logic would be if condition A were true and condition B were false
  3. The outer else-block in your question's logic required condition A to be false -- In this logic if condition A is false we also do action C

Props to Code-Apprentice for straightening me out here. I'd suggest accepting his answer, since he presented it correctly without editing :/

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    Note that "condition A" does not need to be evaluated again. In C++, we have the law of the excluded middle. If "not condition A" is false, then "condition A" is necessarily true. – Code-Apprentice Jul 20 '17 at 03:34
  • 1
    Because of short-circuit evaluation, `B` will be evaluated only if `!A` is false. So both must fail in order to execute the `else` statements. – Code-Apprentice Jul 20 '17 at 03:38
  • Even without short-circuit evaluation `!A || B` is false exactly when both `!A` and `B` are false. Therefore, `A` will be true when the `else` executes. No need to reevaluate `A`. – Code-Apprentice Jul 20 '17 at 03:40
  • @Code-Apprentice Well stink, excellent observation, I've corrected my answer, but suggested yours be accepted. I'm just trying to explain what you already put forward. – Jonathan Mee Jul 20 '17 at 03:55
  • I wish I could give you another up vote for explaining each case. – Code-Apprentice Jul 20 '17 at 03:55
6

Even though there are already good answers, I thought that this approach might be even more intuitive to someone who is new to Boolean algebra then to evaluate a truth table.

First thing you want to do is look, under which conditions you want to execute C. This is the case when (a & b). Also when !a. So you have (a & b) | !a.

If you want to minimize you can go on. Just like in "normal" arithmetic's, you can multiply out.

(a & b) | !a = (a | !a) & (b | !a). a | !a is always true, so you can just cross it out, which leaves you with the minimized result: b | !a. In case the order makes a difference, because you want to check b only if !a is true (for example when !a is a nullpointer check and b is an operation on the pointer like @LordFarquaad pointed out in his comment), you might want to switch the two.

The other case (/* ... */) is will be always executed when c is not executed, so we can just put it in the else case.

Also worth mentioning is that it probably makes sense either way to put action c into a method.

Which leaves us with the following code:

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

This way you can also minimize terms with more operands, which quickly gets ugly with truth tables. Another good approach are Karnaugh maps. But I won't go deeper into this now.

deetz
  • 491
  • 1
  • 7
  • 20
6

In the logic concept, you can solve this problem as follow:

f = a.b + !a
f = ?

As a proven problem, this results in f = !a + b. There are a some ways to prove the problem such as truth table, Karnaugh Map and so on.

So in C based languages you can use as follow:

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

P.S.: Karnaugh Map is also used for more complicate series of conditions. It's a method of simplifying Boolean algebra expressions.

Siyavash Hamdi
  • 2,764
  • 2
  • 21
  • 32
4

To make the code look more like text, use boolean flags. If the logic is especially obscure, add comments.

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
  {
  ...
  }
anatolyg
  • 26,506
  • 9
  • 60
  • 134
3
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}
Ali Faris
  • 17,754
  • 10
  • 45
  • 70
2

I would extract C to a method, and then exit the function as soon as possible in all cases. else clauses with a single thing at the end should almost always be inverted if possible. Here's a step by step example:

Extract C:

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

Invert first if to get rid of first else:

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

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

Get rid of second else:

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

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

D();

And then you can notice that the two cases have the same body and can be combined:

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

D();

Optional things to improve would be:

  • depends on context, but if !A || B is confusing, extract it to one or more variables to explain the intent

  • whichever of C() or D() is the non-exceptional case should go last, so if D() is the exception, then invert the if one last time

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
2

Using flags can also solve this problem

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
styvane
  • 59,869
  • 19
  • 150
  • 156
Spr k
  • 36
  • 6