Se la condizione A è soddisfatta, la condizione B deve essere soddisfatta per poter eseguire l'azione C


148

La mia domanda è:

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

È possibile scrivere il codice di azione C una volta anziché due volte?

Come semplificarlo?


56
Inserire il codice per "azione C" in una funzione?
CinCout

26
Questo è triste, questo non realmente correlato alla domanda C ++ è arrivato a HNQ: /
YSC

2
Grazie a tutti per avermi aiutato! All'inizio, voglio solo assicurarmi che tutto vada bene, quindi ho usato un nidificato if. È perché questo è il modo più semplice che ho indovinato. Cercherò di fare più sforzi dopo aver fatto domande la prossima volta. Auguro a tutti una buona giornata :)
starf15h

13
Questa è un'ottima strategia: scrivere il codice che funziona per primo, quindi preoccuparsi di renderlo elegante ed efficiente in seguito.
Apprendista di codice

3
@Tim l'ho pubblicato come risposta. Per contro, è triste vedere meno voti lì.
CinCout il

Risposte:


400

Il tuo primo passo in questo tipo di problemi è sempre quello di creare una tabella logica.

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

Dopo aver creato la tabella, la soluzione è chiara.

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

Si noti che questa logica, sebbene più breve, potrebbe essere difficile da mantenere per i futuri programmatori.


35
Mi piace molto che tu abbia mostrato la tabella della verità per aiutare l'OP a capire come svilupparlo da solo. Puoi fare un ulteriore passo avanti e spiegare come ottenere l'espressione booleana dalla tabella della verità? Per qualcuno che non conosce la programmazione e la logica booleana, questo probabilmente non è affatto chiaro.
Apprendista di codice il

14
Se la valutazione Bha effetti collaterali, la tabella logica deve tenerne conto.
Yakk - Adam Nevraumont,

79
@Yakk La mia risposta non affronta gli effetti collaterali per due motivi. Innanzitutto, la soluzione ha (per coincidenza) il comportamento corretto degli effetti collaterali. In secondo luogo, e ancora più importante, A e B con effetti collaterali sarebbero un cattivo codice e una discussione su quel caso marginale sarebbe una distrazione per una domanda fondamentalmente sulla logica booleana.
Domanda

52
Forse vale la pena notare, nel caso in cui il A && !Bcaso sia un no-op: !(A && !B)equivale al !A || Bche significa che puoi fare if (!A || B) { /* do action C */ }ed evitare un blocco vuoto.
KRyan,

54
Se if (A && !B)è davvero difficile da mantenere per i futuri programmatori, non c'è davvero alcun aiuto.
Ray

65

Hai due opzioni:

  1. Scrivi una funzione che esegue "azione C".

  2. Riorganizza la tua logica in modo da non avere così tante istruzioni if ​​nidificate. Chiediti quali condizioni causano "l'azione C". Mi sembra che accada quando la "condizione B" è vera o la "condizione A" è falsa. Possiamo scrivere questo come "NOT A OR B". Traducendolo in codice C, otteniamo

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

Per saperne di più su questo tipo di espressioni, suggerisco di cercare su Google "algebra booleana", "logica predicata" e "calcolo predicato". Questi sono argomenti matematici profondi. Non è necessario imparare tutto, solo le basi.

Dovresti anche conoscere la "valutazione del corto circuito". Per questo motivo, l'ordine delle espressioni è importante per duplicare esattamente la logica originale. Mentre B || !Aè logicamente equivalente, usando questo come condizione eseguirà "azione C" quando Bè vero indipendentemente dal valore di A.


15
@Yakk Vedi le leggi di deMorgan.
Apprendista di codice il

@ Apprendista di codice Per favore, perdona il mio cattivo pensiero logico. Vorrei chiedere se ci sono differenze tra (! A || B) e (A &&! B). Sembra che entrambi vadano bene per il mio problema. Intendo l'approccio tuo e di QuestionC.
Starf15h

6
@ Starf15h C'è un'altra differenza cruciale: dove viene eseguita "l'azione C". Questa differenza rende le nostre due soluzioni esattamente equivalenti. Ti suggerisco di google "leggi di deMorgan" che dovrebbe aiutarti a capire cosa sta succedendo qui.
Apprendista di codice

5
Le due soluzioni sono esattamente equivalenti, ma ci può essere una differenza pratica a seconda di cosa esattamente ...è . Se non è affatto niente (cioè, "fai C se queste condizioni sono soddisfatte; altrimenti non fai nulla"), allora questa è chiaramente la soluzione superiore, dato che l' elseaffermazione può semplicemente essere del tutto esclusa.
Janus Bahs Jacquet,

1
Inoltre, a seconda dei nomi di A e B, questa disposizione può essere più leggibile o meno leggibile per un essere umano rispetto alla disposizione di QuestionC.
Michael - Dov'è Clay Shirky il

15

Puoi semplificare l'affermazione in questo modo:

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

Altrimenti metti il ​​codice per 'C' in una funzione separata e chiamalo:

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

7
O più semplicementeif (!A || B)
Tas,

2
Logicamente, ((A && B) ||! A) equivale a (B ||! A)
Apprendista codice

@ Code-Apprentice B || !Arisulterà truesolo se lo Bè true, senza effettivamente verificare a Acausa di cortocircuito
CinCout

1
@CinCout Buon punto. Mentre la mia affermazione è ancora vera dal punto di vista della logica booleana teorica, non ho tenuto conto delle funzionalità degli operatori booleani in corto circuito. Fortunatamente, la mia risposta ha l'ordine corretto.
Apprendista di codice

1
Quindi da un punto di vista logico, l'ordine non ha importanza. Tuttavia, dal punto di vista della manutenzione e della leggibilità, potrebbe esserci un'enorme differenza a seconda di cosa esattamente Ae cosa Brappresenta.
Apprendista di codice il

14

In un linguaggio con pattern matching, puoi esprimere la soluzione in un modo che rifletta più direttamente la tabella di verità nella risposta di QuestionC.

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

Se non si ha familiarità con la sintassi, ogni modello è rappresentato da un | seguito dai valori da abbinare a (a, b) e il carattere di sottolineatura viene utilizzato come carattere jolly per indicare "qualsiasi altro valore". Poiché l'unico caso in cui vogliamo fare qualcosa di diverso dall'azione c è quando a è vero eb è falso, dichiariamo esplicitamente quei valori come primo modello (vero, falso) e quindi facciamo tutto ciò che dovrebbe essere fatto in quel caso. In tutti gli altri casi, passiamo al modello "jolly" e facciamo un'azione c.


10

La dichiarazione del problema:

Se la condizione A è soddisfatta, la condizione B deve essere soddisfatta per poter eseguire l'azione C

descrive l' implicazione : A implica B , una proposizione logica equivalente a !A || B(come menzionato in altre risposte):

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

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

Forse segnalo inlineper C e constexpranche per C ++?
einpoklum,

@einpoklum Non ho approfondito alcuni di questi dettagli perché questa domanda non specificava davvero un linguaggio (ma forniva un esempio con sintassi simile a C), quindi ho dato una risposta con sintassi simile a C. Personalmente userei una macro in modo che la condizione B non venga valutata inutilmente.
jamesdlin,

6

Ugh, anche questo mi ha fatto inciampare, ma come sottolineato da Code-Apprentice abbiamo la garanzia di dover do action Co eseguire il elseblocco nidificato , quindi il codice potrebbe essere semplificato per:

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

Ecco come stiamo colpendo i 3 casi:

  1. Nidificato do action Cnella logica della tua domanda richiesto condition Aecondition B essere true- In questa logica, se raggiungiamo il 2 ° termine nella ifdichiarazione, allora sappiamo che condition Aè truequindi tutto ciò che dobbiamo valutare è che condition Bètrue
  2. Il elseblocco nidificato nella logica della tua domanda condition Adoveva esseretrue ed condition Bessere false- L'unico modo in cui possiamo raggiungere il elseblocco in questa logica sarebbe se condition Afosse truee condition Bfossefalse
  3. Il elseblocco esterno nella logica della tua domanda condition Adoveva essere false- In questa logica se condition Aè falso anche noido action C

Propone all'apprendista di codice per avermi raddrizzato qui. Suggerirei di accettare la sua risposta , poiché l'ha presentata correttamente senza modificarla: /


2
Si noti che non è necessario valutare nuovamente la "condizione A". In C ++, abbiamo la legge del mezzo escluso. Se "non condizione A" è falso, allora "condizione A" è necessariamente vero.
Apprendista di codice

1
A causa della valutazione del corto circuito, Bverrà valutato solo se !Aè falso. Quindi entrambi devono fallire per eseguire le elsedichiarazioni.
Apprendista di codice

Anche senza valutazione del corto circuito !A || Bè falso esattamente quando entrambi !Ae Bsono falsi. Pertanto, Asarà vero quando elseviene eseguito. Non è necessario rivalutare A.
Apprendista di codice il

@ Apprendista di codice Beh, puzza, osservazione eccellente, ho corretto la mia risposta, ma ho suggerito di accettare la tua. Sto solo cercando di spiegare cosa hai già avanzato.
Jonathan Mee,

Vorrei poterti dare un altro voto per aver spiegato ogni caso.
Apprendista di codice il

6

Nel concetto logico, puoi risolvere questo problema come segue:

f = ab +! a
f =?

Come problema provato, questo si traduce in f = !a + b. Ci sono alcuni modi per dimostrare il problema come la tabella della verità, la mappa di Karnaugh e così via.

Quindi nei linguaggi basati su C puoi usare come segue:

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

PS: Karnaugh Map viene utilizzato anche per una serie di condizioni più complicate. È un metodo per semplificare le espressioni algebriche booleane.


6

Anche se ci sono già buone risposte, ho pensato che questo approccio potrebbe essere ancora più intuitivo per qualcuno che non conosce l'algebra booleana piuttosto che valutare una tabella di verità.

La prima cosa che vuoi fare è guardare, a quali condizioni vuoi eseguire C. Questo è il caso in cui (a & b). Anche quando !a. Quindi hai (a & b) | !a.

Se vuoi minimizzare puoi continuare. Proprio come nell'aritmetica "normale", puoi moltiplicare.

(a & b) | !a = (a | !a) & (b | !a). a | ! a è sempre vero, quindi puoi semplicemente cancellarlo, il che ti lascia con il risultato minimizzato:b | !a . Nel caso in cui l'ordine faccia la differenza, perché vuoi controllare b solo se! A è vero (per esempio quando! A è un controllo nullpointer eb è un'operazione sul puntatore come sottolineato da @LordFarquaad nel suo commento), potresti voglio cambiare i due.

L'altro caso (/ * ... * /) verrà sempre eseguito quando c non viene eseguito, quindi possiamo semplicemente inserirlo nell'altro caso.

Vale anche la pena ricordare che probabilmente ha senso in entrambi i casi inserire l'azione c in un metodo.

Il che ci lascia con il seguente codice:

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

In questo modo puoi anche ridurre al minimo i termini con più operandi, che diventa rapidamente brutto con le tabelle di verità. Un altro buon approccio sono le mappe di Karnaugh. Ma non approfondirò ora questo.


4

Per rendere il codice più simile al testo, usa flag booleani. Se la logica è particolarmente oscura, aggiungi commenti.

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

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

2

Vorrei estrarre C da un metodo e quindi uscire dalla funzione il più presto possibile in tutti i casi. elsele clausole con una sola cosa alla fine dovrebbero quasi sempre essere invertite, se possibile. Ecco un esempio passo dopo passo:

Estrai C:

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

Invertire prima ifper sbarazzarsi di prima else:

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

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

Sbarazzarsi del secondo else:

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

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

D();

E poi puoi notare che i due casi hanno lo stesso corpo e possono essere combinati:

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

D();

Le cose opzionali da migliorare sarebbero:

  • dipende dal contesto, ma se !A || Bè confuso, estrarlo in una o più variabili per spiegare l'intento

  • a seconda di quale di C()o D()è il caso non eccezionale, dovrebbe andare scorso, quindi, se D()è l'eccezione, quindi invertito l' ifultima volta


2

L'uso di flag può anche risolvere questo problema

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.