Si la condition A est remplie, la condition B doit être remplie afin de faire l'action C

148

Ma question est:

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

Est-il possible d'écrire simplement le code de l'action C une fois au lieu de deux fois?

Comment le simplifier?

starf15h
la source
56
Mettre le code pour "action C" dans une fonction?
CinCout
26
C'est triste que cette question ne soit pas vraiment liée à C ++ soit arrivée à HNQ: /
YSC
2
Merci à tous de m'aider! Au début, je veux juste m'assurer que tout va bien, j'ai donc utilisé un if imbriqué. C'est parce que c'est la manière la plus simple que j'ai devinée. J'essaierai de faire plus d'efforts après avoir posé des questions la prochaine fois. Je souhaite à tous une bonne journée :)
starf15h
13
C'est une très bonne stratégie: écrire du code qui fonctionne en premier, puis se soucier de le rendre élégant et efficace plus tard.
Code-Apprentice
3
@Tim je l'ai posté comme réponse. En passant, c'est triste de voir moins de votes là-bas.
CinCout

Réponses:

400

Votre première étape dans ce genre de problèmes est toujours de créer une table logique.

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

Une fois que vous avez fait le tableau, la solution est claire.

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

Notez que cette logique, bien que plus courte, peut être difficile à maintenir pour les futurs programmeurs.

QuestionC
la source
35
J'aime beaucoup le fait que vous ayez montré la table de vérité pour aider le PO à comprendre comment le développer lui-même. Pouvez-vous aller plus loin et expliquer comment obtenir l'expression booléenne de la table de vérité? Pour quelqu'un de nouveau dans la programmation et la logique booléenne, ce n'est probablement pas du tout clair.
Code-Apprentice
14
Si l'évaluation Ba des effets secondaires, la table logique doit en tenir compte.
Yakk - Adam Nevraumont
79
@Yakk Ma réponse ne traite pas des effets secondaires pour deux raisons. Premièrement, la solution a (par coïncidence) le comportement correct des effets secondaires. Deuxièmement, et plus important encore, A et B ayant des effets secondaires seraient un mauvais code et une discussion sur ce cas marginal serait une distraction pour une question fondamentalement sur la logique booléenne.
QuestionC
52
Peut-être intéressant de noter, dans le A && !Bcas où le cas est un no-op: !(A && !B)est équivalent à !A || Bce qui signifie que vous pouvez faire if (!A || B) { /* do action C */ }et éviter un bloc vide.
KRyan
54
Si if (A && !B)c'est vraiment difficile à maintenir pour les futurs programmeurs, alors rien ne les aide.
Ray
65

Vous avez deux options:

  1. Écrivez une fonction qui exécute "l'action C".

  2. Réorganisez votre logique afin de ne pas avoir autant d'instructions if imbriquées. Demandez-vous quelles conditions provoquent l '"action C". Il me semble que cela se produit lorsque la "condition B" est vraie ou la "condition A" est fausse. Nous pouvons écrire ceci comme "PAS A OU B". En traduisant cela en code C, nous obtenons

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

Pour en savoir plus sur ce genre d'expressions, je suggère de googler «algèbre booléenne», «logique des prédicats» et «calcul des prédicats». Ce sont des sujets mathématiques profonds. Vous n'avez pas besoin de tout apprendre, juste les bases.

Vous devriez également vous renseigner sur «l'évaluation des courts-circuits». Pour cette raison, l'ordre des expressions est important pour reproduire exactement votre logique d'origine. Alors que B || !Aest logiquement équivalent, utiliser ceci comme condition exécutera "l'action C" quand Best vrai quelle que soit la valeur de A.

Code-Apprenti
la source
15
@Yakk Voir les lois de deMorgan.
Code-Apprentice
@ Code-Apprentice Veuillez pardonner ma mauvaise pensée logique. Je voudrais demander s'il y a une différence entre (! A || B) et (A &&! B). Il semble que les deux conviennent à mon problème. Je veux dire la vôtre et l'approche de QuestionC.
starf15h
6
@ Starf15h Il y a une autre différence cruciale: l'endroit où "l'action C" est exécutée. Cette différence rend nos deux solutions exactement équivalentes. Je vous suggère de rechercher sur Google "les lois de deMorgan" qui devraient vous aider à comprendre ce qui se passe ici.
Code-Apprentice
5
Les deux solutions sont exactement équivalentes, mais il peut y avoir une différence pratique selon ce que ...c'est exactement . Si ce n'est rien du tout (c'est-à-dire «faire C si ces conditions sont remplies; sinon ne rien faire»), alors c'est clairement la meilleure solution, puisque l' elseénoncé peut alors simplement être laissé de côté.
Janus Bahs Jacquet
1
En outre, selon les noms de A et B, cet arrangement peut être plus lisible ou moins lisible pour un humain que l'arrangement de QuestionC.
Michael - Where's Clay Shirky
15

Vous pouvez simplifier la déclaration comme ceci:

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

Sinon, mettez le code pour 'C' dans une fonction séparée et appelez-le:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}
CinCout
la source
7
Ou plus simplementif (!A || B)
Tas
2
Logiquement, ((A && B) ||! A) équivaut à (B ||! A)
Code-Apprentice
@ Code-Apprentice B || !Ane résultera trueque si Bc'est le cas true, sans réellement vérifier en Araison d'un court-circuit
CinCout
1
@CinCout Bon point. Bien que ma déclaration soit toujours vraie du point de vue de la logique booléenne théorique, je n'ai pas pris en compte les aspects pratiques des opérateurs booléens de court-circuit. Heureusement, ma propre réponse est dans le bon ordre.
Code-Apprentice
1
Donc, d'un point de vue logique, l'ordre n'a pas d'importance. Cependant, du point de vue de la maintenance et de la lisibilité, il peut y avoir une énorme différence en fonction de ce que exactement Aet de ce que Breprésente.
Code-Apprentice
14

Dans un langage avec correspondance de modèles, vous pouvez exprimer la solution d'une manière qui reflète plus directement la table de vérité dans la réponse de QuestionC.

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

Si vous n'êtes pas familier avec la syntaxe, chaque motif est représenté par un | suivi des valeurs à faire correspondre avec (a, b), et le trait de soulignement est utilisé comme caractère générique pour signifier «toutes les autres valeurs». Puisque le seul cas où nous voulons faire autre chose que l'action c est quand a est vrai et b est faux, nous déclarons explicitement ces valeurs comme le premier modèle (vrai, faux) et faisons ensuite tout ce qui doit être fait dans ce cas. Dans tous les autres cas, nous passons au modèle "joker" et faisons l'action c.

Aaron M. Eshbach
la source
10

L'énoncé du problème:

Si la condition A est remplie, la condition B doit être remplie afin de faire l'action C

décrit l' implication : A implique B , une proposition logique équivalente à !A || B(comme mentionné dans d'autres réponses):

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

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}
Jamesdlin
la source
Peut-être le marquer inlinepour C et constexpraussi pour C ++?
einpoklum
@einpoklum Je ne suis pas entré dans certains de ces détails parce que cette question ne spécifiait pas vraiment de langage (mais donnait un exemple avec une syntaxe de type C), j'ai donc donné une réponse avec une syntaxe de type C. Personnellement, j'utiliserais une macro pour que la condition B ne soit pas évaluée inutilement.
jamesdlin
6

Ugh, cela m'a fait trébucher aussi, mais comme l'a souligné Code-Apprentice, nous sommes do action Csûrs d'avoir besoin ou d'exécuter le elsebloc imbriqué , ainsi le code pourrait être simplifié pour:

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

Voici comment nous abordons les 3 cas:

  1. Le niché do action Cdans la logique de votre question exige condition Aet condition Bêtre true- Dans cette logique, si nous atteignons le 2 ème terme de la ifdéclaration-alors nous savons que condition Ac'est truedonc tout ce que nous devons évaluer c'est que condition Bc'esttrue
  2. Le else-bloc imbriqué dans la logique de votre question condition Adevait être trueet condition Bêtre false- La seule façon d'atteindre le else-bloc dans cette logique serait si condition Aétaient trueet condition Bétaientfalse
  3. Le elsebloc externe dans la logique de votre question doit condition Aêtre false- Dans cette logique, si condition Aest faux, nousdo action C

Props à Code-Apprentice pour me redresser ici. Je suggère d'accepter sa réponse , car il l'a présentée correctement sans la modifier: /

Jonathan Mee
la source
2
Notez que la "condition A" n'a pas besoin d'être évaluée à nouveau. En C ++, nous avons la loi du milieu exclu. Si "pas la condition A" est fausse, alors la "condition A" est nécessairement vraie.
Code-Apprentice
1
En raison de l'évaluation de court-circuit, Bne sera évalué que si !Aest faux. Les deux doivent donc échouer pour exécuter les elseinstructions.
Code-Apprentice
Même sans évaluation de court-circuit, !A || Bc'est exactement faux quand les deux !Aet Bsont faux. Par conséquent, Asera vrai lors de l' elseexécution. Pas besoin de réévaluer A.
Code-Apprentice
@ Code-Apprentice Eh bien puant, excellente observation, j'ai corrigé ma réponse, mais j'ai suggéré que la vôtre soit acceptée. J'essaie simplement d'expliquer ce que vous avez déjà avancé.
Jonathan Mee
J'aimerais pouvoir vous donner un autre vote pour expliquer chaque cas.
Code-Apprentice
6

Dans le concept logique, vous pouvez résoudre ce problème comme suit:

f = ab +! a
f =?

En tant que problème avéré, il en résulte f = !a + b. Il existe quelques moyens de prouver le problème, tels que la table de vérité, la carte de Karnaugh , etc.

Donc, dans les langages basés sur C, vous pouvez utiliser comme suit:

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

PS: Karnaugh Map est également utilisé pour des séries de conditions plus complexes. C'est une méthode de simplification des expressions d'algèbre booléenne.

Siyavash Hamdi
la source
6

Même s'il y a déjà de bonnes réponses, j'ai pensé que cette approche pourrait être encore plus intuitive pour quelqu'un qui est nouveau dans l'algèbre booléenne que pour évaluer une table de vérité.

La première chose que vous voulez faire est de regarder, dans quelles conditions vous voulez exécuter C. C'est le cas quand (a & b). Aussi quand !a. Donc vous avez (a & b) | !a.

Si vous voulez minimiser, vous pouvez continuer. Tout comme dans l'arithmétique "normale", vous pouvez multiplier.

(a & b) | !a = (a | !a) & (b | !a). a | ! a est toujours vrai, de sorte que vous pouvez simplement biffer, qui vous laisse avec le résultat réduit: b | !a. Dans le cas où l'ordre fait une différence, parce que vous voulez vérifier b uniquement si! A est vrai (par exemple quand! A est une vérification de pointeur nul et b est une opération sur le pointeur comme @LordFarquaad l'a souligné dans son commentaire), vous pourriez voulez changer les deux.

L'autre cas (/ * ... * /) est sera toujours exécuté lorsque c n'est pas exécuté, donc nous pouvons simplement le mettre dans le cas else.

Il convient également de mentionner qu'il est probablement logique de mettre l'action c dans une méthode.

Ce qui nous laisse avec le code suivant:

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

De cette façon, vous pouvez également minimiser les termes avec plus d'opérandes, ce qui devient rapidement laid avec les tables de vérité. Les cartes de Karnaugh sont une autre bonne approche. Mais je ne vais pas approfondir cela maintenant.

deetz
la source
4

Pour que le code ressemble plus à du texte, utilisez des indicateurs booléens. Si la logique est particulièrement obscure, ajoutez des commentaires.

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
la source
3
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}
Ali
la source
2

Je voudrais extraire C dans une méthode, puis quitter la fonction dès que possible dans tous les cas. elseles clauses avec une seule chose à la fin devraient presque toujours être inversées si possible. Voici un exemple étape par étape:

Extrait C:

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

Inversez d'abord ifpour vous débarrasser de else:

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

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

Débarrassez-vous du second else:

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

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

D();

Et puis vous pouvez remarquer que les deux boîtiers ont le même corps et peuvent être combinés:

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

D();

Les choses facultatives à améliorer seraient:

  • dépend du contexte, mais si cela !A || Bprête à confusion, extrayez-le dans une ou plusieurs variables pour expliquer l'intention

  • quel que C()soit D()le cas non exceptionnel ou le cas non exceptionnel qui doit passer en dernier, si D()c'est l'exception, alors inversez leif dernière fois

Dave Cousineau
la source
2

L'utilisation d'indicateurs peut également résoudre ce problème

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
Spr k
la source