Jeśli spełniony jest warunek A, warunek B musi zostać dopasowany, aby wykonać działanie C.

148

Moje pytanie brzmi:

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

Czy można po prostu napisać kod akcji C raz zamiast dwa razy?

Jak to uprościć?

starf15h
źródło
56
Umieścić kod „akcji C” w funkcji?
CinCout
26
To smutne, że pytanie nie jest tak naprawdę związane z C ++, które dotarło do HNQ: /
YSC
2
Dziękuję wszystkim za pomoc! Na początku chcę się tylko upewnić, że wszystko jest w porządku, więc użyłem zagnieżdżonego if. To dlatego, że jest to najprostszy sposób, w jaki się domyśliłem. Postaram się włożyć więcej wysiłku po następnym zadaniu pytań. Życzę wszystkim miłego dnia :)
starf15h
13
To bardzo dobra strategia: najpierw napisz kod, który działa, a potem martw się, czy później będzie elegancki i wydajny.
Code-Apprentice
3
@Tim Opublikowałem to jako odpowiedź. Na marginesie, smutno jest widzieć tam mniej głosów.
CinCout

Odpowiedzi:

400

Pierwszym krokiem w tego rodzaju problemach jest zawsze utworzenie tablicy logicznej.

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

Po przygotowaniu stołu rozwiązanie jest jasne.

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

Zwróć uwagę, że ta logika, choć krótsza, może być trudna do utrzymania dla przyszłych programistów.

Pytanie C.
źródło
35
Naprawdę podoba mi się, że pokazałeś tabelę prawdy, aby pomóc PO zrozumieć, jak samodzielnie to opracować. Czy możesz pójść o krok dalej i wyjaśnić, jak uzyskać wyrażenie boolowskie z tabeli prawdy? Dla kogoś nowego w programowaniu i logice boolowskiej prawdopodobnie nie jest to wcale jasne.
Code-Apprentice,
14
Jeśli ocena Bma skutki uboczne, tablica logiczna musi to uwzględnić.
Yakk - Adam Nevraumont
79
@Yakk Moja odpowiedź nie dotyczy skutków ubocznych z dwóch powodów. Po pierwsze, rozwiązanie ma (przypadkowo) prawidłowe działanie niepożądane. Po drugie, i co ważniejsze, A i B mające skutki uboczne byłyby złym kodem, a dyskusja na temat tego pobocznego przypadku byłaby rozproszeniem dla pytania zasadniczo o logikę boolowską.
Pytanie C,
52
Być może warto zauważyć, na wypadek, gdyby A && !Bsprawa nie była operacją: !(A && !B)jest odpowiednikiem tego, !A || Bco oznacza, że ​​możesz zrobić if (!A || B) { /* do action C */ }i uniknąć pustego bloku.
KRyan
54
Jeśli if (A && !B)jest to naprawdę trudne do utrzymania dla przyszłych programistów, to naprawdę nie ma dla nich żadnej pomocy.
Ray
65

Masz dwie możliwości:

  1. Napisz funkcję, która wykonuje „akcję C”.

  2. Zmień układ logiki tak, aby nie było tylu zagnieżdżonych instrukcji if. Zadaj sobie pytanie, jakie warunki powodują wystąpienie „działania C”. Wydaje mi się, że dzieje się tak, gdy „warunek B” jest prawdziwy lub „warunek A” jest fałszywy. Możemy to zapisać jako „NIE A LUB B”. Tłumacząc to na kod C, otrzymujemy

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

Aby dowiedzieć się więcej o tego rodzaju wyrażeniach, sugeruję wpisanie w wyszukiwarkę „algebry boolowskiej”, „logiki predykatów” i „rachunku predykatów”. To są głębokie zagadnienia matematyczne. Nie musisz uczyć się wszystkiego, tylko podstaw.

Warto również zapoznać się z „oceną zwarcia”. Z tego powodu kolejność wyrażeń jest ważna, aby dokładnie powielić oryginalną logikę. Chociaż B || !Ajest logicznie równoważne, użycie tego jako warunku spowoduje wykonanie „akcji C”, gdy Bjest prawdziwe, niezależnie od wartości A.

Code-Apprentice
źródło
15
@Yakk Zobacz prawa deMorgana.
Code-Apprentice
@ Code-Apprentice Proszę wybaczyć moje złe logiczne myślenie. Chciałbym zapytać, czy jest jakaś różnica między (! A || B) a (A &&! B). Wygląda na to, że oba są w porządku dla mojego problemu. Mam na myśli podejście twoje i QuestionC.
starf15h
6
@ Starf15h Jest jeszcze jedna istotna różnica: gdzie wykonywana jest „akcja C”. Ta różnica sprawia, że ​​nasze dwa rozwiązania są dokładnie równoważne. Proponuję wygooglować „Prawa deMorgana”, co powinno pomóc ci zrozumieć, co się tutaj dzieje.
Code-Apprentice
5
Te dwa rozwiązania są dokładnie równoważne, ale może istnieć praktyczna różnica w zależności od tego, co dokładnie ...jest . Jeśli to w ogóle nic (tj. „Wykonaj C, jeśli te warunki są spełnione; w przeciwnym razie nie rób nic”), to jest to oczywiście lepsze rozwiązanie, ponieważ elsemożna wtedy po prostu całkowicie pominąć stwierdzenie.
Janus Bahs Jacquet
1
Ponadto, w zależności od imion A i B, układ ten może być bardziej czytelny lub mniej czytelny dla człowieka niż układ QuestionC.
Michael - Where's Clay Shirky
15

Możesz uprościć to stwierdzenie w następujący sposób:

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

W przeciwnym razie umieść kod „C” w osobnej funkcji i wywołaj ją:

DoActionC()
{
    ....
    // code for Action C
}
if (condition A)
{
    if(condition B)
      {
         DoActionC(); // call the function
      }
    else
      ...
}
else
{
   DoActionC(); // call the function
}
CinCout
źródło
7
Lub prościejif (!A || B)
Tas
2
Logicznie rzecz biorąc, ((A && B) ||! A) jest równoważne z (B ||! A)
Code-Apprentice,
@ Code-Apprentice B || !Aspowoduje, że truetylko wtedy , gdy Bjest true, bez faktycznego sprawdzania z Apowodu zwarcia
CinCout
1
@CinCout Słuszna uwaga. Chociaż moje stwierdzenie jest nadal prawdziwe z teoretycznej perspektywy logiki boolowskiej, nie wziąłem pod uwagę praktyczności operatorów logicznych zwarciowych. Na szczęście moja własna odpowiedź ma poprawną kolejność.
Code-Apprentice
1
Więc z logicznego punktu widzenia kolejność nie ma znaczenia. Jednak z punktu widzenia konserwacji i czytelności może istnieć ogromna różnica w zależności od tego, co dokładnie Ai co Boznacza.
Code-Apprentice
14

W języku z dopasowywaniem wzorców możesz wyrazić rozwiązanie w sposób, który bardziej bezpośrednio odzwierciedla tabelę prawdy w odpowiedzi na pytanie.

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

Jeśli nie znasz składni, każdy wzorzec jest reprezentowany przez | po którym następują wartości do dopasowania z (a, b), a podkreślenie jest używane jako symbol wieloznaczny oznaczający „wszelkie inne wartości”. Ponieważ jedynym przypadkiem, w którym chcemy zrobić coś innego niż akcja c, jest sytuacja, w której a jest prawdą, a b jest fałszem, jawnie określamy te wartości jako pierwszy wzorzec (prawda, fałsz), a następnie robimy wszystko, co należy zrobić w takim przypadku. We wszystkich innych przypadkach przechodzimy do wzorca „wieloznacznego” i wykonujemy czynność c.

Aaron M. Eshbach
źródło
10

Opis problemu:

Jeśli spełniony jest warunek A, warunek B musi zostać dopasowany, aby wykonać działanie C.

opisuje implikację : A implikuje B , logiczną propozycję równoważną !A || B(jak wspomniano w innych odpowiedziach):

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

if (implies(/* condition A */,
            /* condition B */))
{
    /* do action C */
}
jamesdlin
źródło
Może oznaczyć to inlinedla C, a constexprtakże dla C ++?
einpoklum
@einpoklum Nie wdałem się w niektóre z tych szczegółów, ponieważ to pytanie tak naprawdę nie określało języka (ale podałem przykład ze składnią podobną do C), więc podałem odpowiedź za pomocą składni podobnej do C. Osobiście użyłbym makra, aby warunek B nie był niepotrzebnie oceniany.
jamesdlin
6

Ugh, to też mnie zaskoczyło , ale jak wskazał Code-Apprentice, mamy gwarancję, że będziemy potrzebować do action Club uruchomić zagnieżdżony elseblok, więc kod można uprościć do:

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

Oto jak zajmujemy się trzema przypadkami:

  1. Zagnieżdżonego do action Cw logice swojej pytanie jest wymagana condition Ai condition Bbędzie true- w tym logiki, jeśli osiągniemy 2 nd termin w if-statement czym wiemy, że condition Ajest truew ten sposób wszystko, czego potrzebujemy, aby ocenić to, żecondition B jesttrue
  2. Zagnieżdżony else-blok w logice twojego pytania, który musiał condition Abyć truei condition Bbyć false- Jedynym sposobem, w jaki możemy dotrzeć do else-bloku w tej logice, byłoby gdyby condition Abyły truei condition Bbyłyfalse
  3. Zewnętrzny elseblok w logice twojego pytania musi condition Abyć false- W tej logice, jeśli condition Ajest fałszywa, my równieżdo action C

Podziękowania dla Code-Apprentice za wyprostowanie mnie tutaj. Proponuję przyjąć jego odpowiedź , ponieważ przedstawił ją poprawnie bez edycji: /

Jonathan Mee
źródło
2
Należy zauważyć, że „warunek A” nie musi być ponownie oceniany. W C ++ mamy prawo wyłączonego środka. Jeśli „nie warunek A” jest fałszywy, to „warunek A” jest koniecznie prawdziwy.
Code-Apprentice
1
Ze względu na ocenę zwarcia Bzostanie oszacowany tylko wtedy, gdy !Ajest fałszywy. Więc oba muszą zawieść, aby wykonać elseinstrukcje.
Code-Apprentice,
Nawet bez oceny zwarcia !A || Bjest fałszywa dokładnie wtedy, gdy oba !Ai Bsą fałszywe. Dlatego Abędzie prawdą, gdy elsewykonuje. Nie ma potrzeby ponownej oceny A.
Code-Apprentice
@ Code-Apprentice Cóż, smród, doskonała obserwacja, poprawiłem odpowiedź, ale zasugerowałem, aby twoja została przyjęta. Próbuję tylko wyjaśnić, co już przedstawiłeś.
Jonathan Mee,
Chciałbym móc oddać panu kolejny głos za wyjaśnienie każdej sprawy.
Code-Apprentice,
6

W koncepcji logiki możesz rozwiązać ten problem w następujący sposób:

f = ab +! a
f =?

Jako udowodniony problem powoduje to f = !a + b. Istnieje kilka sposobów udowodnienia problemu, takich jak tabela prawdy, mapa Karnaugha i tak dalej.

Tak więc w językach opartych na C możesz używać w następujący sposób:

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

PS: Mapa Karnaugh jest również używana w przypadku bardziej skomplikowanych serii warunków. Jest to metoda upraszczania wyrażeń algebry Boole'a.

Siyavash Hamdi
źródło
6

Chociaż są już dobre odpowiedzi, pomyślałem, że to podejście może być jeszcze bardziej intuicyjne dla kogoś, kto jest nowy w algebrze Boole'a, niż ocena tabeli prawdy.

Pierwszą rzeczą, którą chcesz zrobić, jest sprawdzenie, w jakich warunkach chcesz wykonać C. Tak jest w przypadku, gdy (a & b). Również kiedy !a. Więc masz(a & b) | !a .

Jeśli chcesz zminimalizować, możesz kontynuować. Tak jak w „normalnej” arytmetyce, możesz pomnożyć.

(a & b) | !a = (a | !a) & (b | !a). a | ! a jest zawsze prawdziwe, więc możesz je po prostu przekreślić, co pozostawi zminimalizowany wynik:b | !a . W przypadku, gdy kolejność ma znaczenie, ponieważ chcesz sprawdzić b tylko wtedy, gdy! A jest prawdziwe (na przykład, gdy! A jest sprawdzeniem zerowym, a b jest operacją na wskaźniku, jak @LordFarquaad wskazany w swoim komentarzu), możesz chcę zamienić te dwa.

Drugi przypadek (/ * ... * /) będzie zawsze wykonywany, gdy c nie zostanie wykonane, więc możemy po prostu umieścić go w innym przypadku.

Warto również wspomnieć, że prawdopodobnie ma sens umieszczenie akcji c w metodzie.

Co pozostawia nam następujący kod:

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

W ten sposób możesz również zminimalizować terminy z większą liczbą operandów, co szybko staje się brzydkie w przypadku tablic prawdy. Innym dobrym podejściem są mapy Karnaugh. Ale nie będę teraz zagłębiał się w to.

deetz
źródło
4

Aby kod wyglądał bardziej jak tekst, użyj flag logicznych. Jeśli logika jest szczególnie niejasna, dodaj komentarze.

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
źródło
3
if((A && B ) || !A)
{
  //do C
}
else if(!B)
{
  //...
}
Ali
źródło
2

Wyodrębniłbym C do metody, a następnie zamknąłbym tę funkcję tak szybko, jak to możliwe we wszystkich przypadkach. elseklauzule z jedną rzeczą na końcu powinny być prawie zawsze w miarę możliwości odwrócone. Oto przykład krok po kroku:

Wyciąg C:

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

Odwróć najpierw, ifaby pozbyć się pierwszego else:

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

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

Pozbądź się drugiego else:

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

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

D();

A potem możesz zauważyć, że te dwie sprawy mają to samo ciało i można je łączyć:

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

D();

Opcjonalne rzeczy do poprawy to:

  • zależy od kontekstu, ale jeśli !A || Bjest mylący, wyodrębnij go do jednej lub kilku zmiennych, aby wyjaśnić cel

  • w zależności od C()czy D()to nie przypadek wyjątkowy powinien przejść ostatni, więc jeśli D()jest wyjątek, a następnie odwrócić ifraz ostatni

Dave Cousineau
źródło
2

Używanie flag może również rozwiązać ten problem

int flag = 1; 
if ( condition A ) {
    flag = 2;
    if( condition B ) {
        flag = 3;
    }
}
if(flag != 2) { 
    do action C 
}
Spr k
źródło