ChatGPT解决这个技术问题 Extra ChatGPT

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

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?

Put the code for "action C" in a function?
This is sad this not really related to C++ question got to HNQ :/
Thank You everyone for helping me! In the beginning, I just want to make sure everything is alright so I used a nested if. It is because this is the simplest way i guessed. I will try to put more efforts after asking questions next time. Wish everyone have a nice day:)
That is a very good strategy: write code that works first, then worry about making it elegant and efficient later.
@Tim I did post it as an answer. On a side note, it's sad to see less votes there.

Q
QuestionC

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.


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.
If evaluating B has side effects the logic table has to account for that.
@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.
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.
If if (A && !B) is truly difficult for future programmers to maintain, then there's really no helping them.
C
Code-Apprentice

You have two options:

Write a function that performs "action C". 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.


@Yakk See deMorgan's Laws.
@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 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.
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.
Also, depending on the names of A and B, this arrangement may be more readable or less readable to a human than QuestionC's arrangement.
M
Michael

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
}

Or more simply if (!A || B)
Logically, ((A&&B) || !A) is equivalent to (B || !A)
@Code-Apprentice B || !A will result to true only if B is true, without actually checking for A due to short-circuiting
@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.
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.
A
Aaron M. Eshbach

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.


j
jamesdlin

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 */
}

Perhaps mark it inline for C and constexpr as well for C++?
@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.
J
Jonathan Mee

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:

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 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 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 :/


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.
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.
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 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.
I wish I could give you another up vote for explaining each case.
S
Siyavash Hamdi

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.


d
deetz

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.


a
anatolyg

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

A
Ali Faris
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}

D
Dave Cousineau

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


s
styvane

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 
}