So verbessern Sie die Logik, um zu überprüfen, ob 4 boolesche Werte in einigen Fällen übereinstimmen

118

Ich habe vier boolWerte:

bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;

Die akzeptablen Werte sind:

         Scenario 1 | Scenario 2 | Scenario 3
bValue1: true       | true       | true
bValue2: true       | true       | false
bValue3: true       | true       | false
bValue4: true       | false      | false

So ist dieses Szenario beispielsweise nicht akzeptabel:

bValue1: false
bValue2: true
bValue3: true
bValue4: true

Im Moment habe ich mir diese ifAussage ausgedacht, um schlechte Szenarien zu erkennen:

if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
   ((bValue3 && (!bValue2 || !bValue1)) ||
   (bValue2 && !bValue1) ||
   (!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}

Kann diese Anweisungslogik verbessert / vereinfacht werden?

Andrew Truckle
quelle
8
Ich würde eine Tabelle anstelle einer komplexen ifAnweisung verwenden. Da es sich um boolesche Flags handelt, können Sie außerdem jedes Szenario als Konstante modellieren und dagegen prüfen.
Zdeslav Vojkovic
3
if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
mch
14
Was sind die Szenarien eigentlich? Oft werden die Dinge viel einfacher, wenn Sie nur Eigennamen bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
angeben
6
Mit aussagekräftigen Namen können Sie jede komplexe Bedingung in eine Methode extrahieren und diese Methode in der if-Bedingung aufrufen. Es wäre viel besser lesbar und wartbar. Beispiel: Schauen Sie sich das Beispiel im Link an. refactoring.guru/decompose-conditional
Hardik Modha
5
Zu
Ihrer Information

Antworten:

195

Ich würde die Lesbarkeit anstreben: Sie haben nur 3 Szenarien, behandeln Sie sie mit 3 separaten ifs:

bool valid = false;
if (bValue1 && bValue2 && bValue3 && bValue4)
    valid = true; //scenario 1
else if (bValue1 && bValue2 && bValue3 && !bValue4)
    valid = true; //scenario 2
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
    valid = true; //scenario 3

Einfach zu lesen und zu debuggen, IMHO. Sie können auch eine Variable zuweisen, whichScenariowährend Sie mit dem fortfahren if.

Mit nur 3 Szenarien würde ich mich nicht für etwas entscheiden wie "Wenn die ersten 3 Werte wahr sind, kann ich es vermeiden, den vierten Wert zu überprüfen": Es wird Ihren Code schwieriger zu lesen und zu warten machen.

Keine elegante Lösung vielleicht sicherlich, aber in diesem Fall ist ok: einfach und lesbar.

Wenn Ihre Logik komplizierter wird, werfen Sie diesen Code weg und überlegen Sie, etwas mehr zu verwenden, um verschiedene verfügbare Szenarien zu speichern (wie Zladeck vorschlägt).

Ich mag den ersten Vorschlag in dieser Antwort sehr : einfach zu lesen, nicht fehleranfällig, wartbar

(Fast) off Topic:

Ich schreibe hier bei StackOverflow nicht viele Antworten. Es ist wirklich lustig, dass die oben akzeptierte Antwort bei weitem die am meisten geschätzte Antwort in meiner Geschichte ist (hatte nie mehr als 5-10 Upvotes, bevor ich denke), während sie eigentlich nicht das ist, was ich normalerweise für den "richtigen" Weg halte.

Aber Einfachheit ist oft "der richtige Weg", viele Leute scheinen das zu denken und ich sollte es mehr denken als ich :)

Gian Paolo
quelle
1
sicher @hessamhedieh, es ist nur für eine kleine Anzahl verfügbarer Szenarien in Ordnung. Wie gesagt, wenn die Dinge komplizierter werden, suchen Sie besser nach etwas anderem
Gian Paolo
4
Dies kann weiter vereinfacht werden, indem alle Bedingungen für validund in den Initialisierer gestapelt und getrennt werden ||, anstatt validin separaten Anweisungsblöcken zu mutieren . Ich kann kein Beispiel in den Kommentar einfügen, aber Sie können die ||Operatoren links vertikal ausrichten , um dies sehr deutlich zu machen. Die einzelnen Bedingungen sind bereits so weit in Klammern gesetzt, wie sie (für if) sein müssen, sodass Sie den Ausdrücken keine Zeichen hinzufügen müssen, die über das hinausgehen, was bereits vorhanden ist.
Leushenko
1
@Leushenko, ich denke, dass das Mischen von Klammern && und || Die Bedingungen sind ziemlich fehleranfällig (jemand in einer anderen Antwort sagte, es sei ein Fehler in Klammern im Code in OP aufgetreten, möglicherweise wurde er korrigiert). Die richtige Ausrichtung kann sicher helfen. Aber was ist der Vorteil? besser lesbar? einfacher zu pflegen? Das glaube ich nicht. Nur meine Meinung natürlich. Und seien Sie sicher, ich hasse es wirklich, viele Wenns im Code zu haben.
Gian Paolo
3
Ich hätte es in ein Paket eingewickelt, if($bValue1)da dies immer wahr sein muss und technisch eine geringfügige Leistungsverbesserung zulässt (obwohl es sich hier um vernachlässigbare Beträge handelt).
Martijn
2
FWIW: Es gibt nur 2 Szenarien: Die ersten 2 sind das gleiche Szenario und hängen nicht vonbValue4
Dancrumb
123

Ich würde auf Einfachheit und Lesbarkeit abzielen.

bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
bool scenario2 = bValue1 && bValue2 && bValue3 && !bValue4;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

if (scenario1 || scenario2 || scenario3) {
    // Do whatever.
}

Stellen Sie sicher, dass Sie die Namen der Szenarien sowie die Namen der Flags durch etwas Beschreibendes ersetzen. Wenn es für Ihr spezifisches Problem sinnvoll ist, können Sie diese Alternative in Betracht ziehen:

bool scenario1or2 = bValue1 && bValue2 && bValue3;
bool scenario3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

if (scenario1or2 || scenario3) {
    // Do whatever.
}

Was hier wichtig ist, ist keine Prädikatenlogik. Es beschreibt Ihre Domain und drückt Ihre Absicht klar aus. Der Schlüssel hier ist, allen Eingaben und Zwischenvariablen gute Namen zu geben. Wenn Sie keine guten Variablennamen finden, kann dies ein Zeichen dafür sein, dass Sie das Problem falsch beschreiben.

Anders
quelle
3
+1 Das hätte ich auch getan. Genau wie @RedFilter hervorhebt und im Gegensatz zur akzeptierten Antwort ist dies selbstdokumentierend. Es ist viel besser, den Szenarien in einem separaten Schritt einen eigenen Namen zu geben.
Andreas
105

Wir können eine Karnaugh-Karte verwenden und Ihre Szenarien auf eine logische Gleichung reduzieren. Ich habe den Online Karnaugh Map Solver mit Schaltung für 4 Variablen verwendet.

Geben Sie hier die Bildbeschreibung ein

Dies ergibt:

Geben Sie hier die Bildbeschreibung ein

Ändern A, B, C, Dzu bValue1, bValue2, bValue3, bValue4, dies ist nichts anderes als:

bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4

So wird Ihre ifAussage:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Karnaugh-Karten sind besonders nützlich, wenn Sie viele Variablen und viele Bedingungen haben, die ausgewertet werden sollten true.
  • Nachdem Sie die trueSzenarien auf eine logische Gleichung reduziert haben , empfiehlt es sich, relevante Kommentare hinzuzufügen, die die trueSzenarien angeben .
PW
quelle
96
Obwohl technisch korrekt, erfordert dieser Code viele Kommentare, um einige Monate später von einem anderen Entwickler bearbeitet zu werden.
Zdeslav Vojkovic
22
@ ZdeslavVojkovic: Ich würde nur einen Kommentar mit der Gleichung hinzufügen. //!(ABC + AB'C'D') (By K-Map logic). Das wäre ein guter Zeitpunkt für den Entwickler, K-Maps zu lernen, wenn er sie noch nicht kennt.
PW
11
Ich stimme dem zu, aber IMO ist das Problem, dass es nicht eindeutig der Problemdomäne zugeordnet ist, dh wie jede Bedingung einem bestimmten Szenario zugeordnet ist, was es schwierig macht, Änderungen / Erweiterungen vorzunehmen. Was passiert , wenn es Eund FBedingungen und 4 neue Szenarien? Wie lange dauert es, diese ifAnweisung korrekt zu aktualisieren ? Wie prüft die Codeüberprüfung, ob sie in Ordnung ist oder nicht? Das Problem liegt nicht auf der technischen Seite, sondern auf der "geschäftlichen" Seite.
Zdeslav Vojkovic
7
Ich denke, Sie können Folgendes herausrechnen A: ABC + AB'C'D' = A(BC + B'C'D')(Dies kann sogar berücksichtigt werden, A(B ^ C)'(C + D')obwohl ich vorsichtig sein würde , wenn ich diese "Vereinfachung" nenne).
Maciej Piechotka
28
@PW Dieser Kommentar scheint ungefähr so ​​verständlich wie der Code und ist daher ein bisschen sinnlos. Ein besserer Kommentar würde erklären, wie Sie tatsächlich zu dieser Gleichung gekommen sind, dh, dass die Anweisung für TTTT, TTTF und TFFF ausgelöst werden sollte. An diesem Punkt können Sie diese drei Bedingungen genauso gut in den Code schreiben und benötigen überhaupt keine Erklärung.
Bernhard Barker
58

Die eigentliche Frage hier ist: Was passiert, wenn ein anderer Entwickler (oder sogar Autor) diesen Code einige Monate später ändern muss?

Ich würde vorschlagen, dies als Bit-Flags zu modellieren:

const int SCENARIO_1 = 0x0F; // 0b1111 if using c++14
const int SCENARIO_2 = 0x0E; // 0b1110
const int SCENARIO_3 = 0x08; // 0b1000

bool bValue1 = true;
bool bValue2 = false;
bool bValue3 = false;
bool bValue4 = false;

// boolean -> int conversion is covered by standard and produces 0/1
int scenario = bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
bool match = scenario == SCENARIO_1 || scenario == SCENARIO_2 || scenario == SCENARIO_3;
std::cout << (match ? "ok" : "error");

Wenn es viel mehr Szenarien oder mehr Flags gibt, ist ein Tabellenansatz lesbarer und erweiterbarer als die Verwendung von Flags. Für die Unterstützung eines neuen Szenarios ist nur eine weitere Zeile in der Tabelle erforderlich.

int scenarios[3][4] = {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false},
};

int main()
{
  bool bValue1 = true;
  bool bValue2 = false;
  bool bValue3 = true;
  bool bValue4 = true;
  bool match = false;

  // depending on compiler, prefer std::size()/_countof instead of magic value of 4
  for (int i = 0; i < 4 && !match; ++i) {
    auto current = scenarios[i];
    match = bValue1 == current[0] && 
            bValue2 == current[1] && 
            bValue3 == current[2] && 
            bValue4 == current[3];
  }

  std::cout << (match ? "ok" : "error");
}
Zdeslav Vojkovic
quelle
4
Nicht die wartbarste, vereinfacht aber definitiv die if-Bedingung. Ein paar Kommentare zu den bitweisen Operationen zu hinterlassen, ist hier imo eine absolute Notwendigkeit.
Adam Zahran
6
IMO, Tabelle ist der beste Ansatz, da sie mit zusätzlichen Szenarien und Flags besser skaliert werden kann.
Zdeslav Vojkovic
Ich mag Ihre erste Lösung, leicht zu lesen und offen für Änderungen. Ich würde 2 Verbesserungen vornehmen: 1: Zuweisen von Werten zu Szenario SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;X mit einer expliziten Angabe der verwendeten Booleschen Werte, z. B. 2: Vermeiden von SCENARIO_X-Variablen und Speichern aller verfügbaren Szenarien in a <std::set<int>. Das Hinzufügen eines Szenarios ist nur mySet.insert( true << 3 | false << 2 | true << 1 | false;ein kleiner Overkill für nur drei Szenarien. OP akzeptierte die schnelle, schmutzige und einfache Lösung, die ich in meiner Antwort vorgeschlagen hatte.
Gian Paolo
4
Wenn Sie C ++ 14 oder höher verwenden, würde ich vorschlagen, stattdessen Binärliterale für die erste Lösung zu verwenden - 0b1111, 0b1110 und 0b1000 sind viel klarer. Sie können dies wahrscheinlich auch mit der Standardbibliothek ( std::find?) Ein wenig vereinfachen .
Bernhard Barker
2
Ich finde, dass binäre Literale hier eine minimale Voraussetzung sind, um den ersten Code sauber zu machen. In seiner jetzigen Form ist es völlig kryptisch. Beschreibende Kennungen könnten helfen, aber da bin ich mir nicht einmal sicher. Tatsächlich erscheinen scenariomir die Bitoperationen zum Erzeugen des Werts als unnötig fehleranfällig.
Konrad Rudolph
27

Meine vorherige Antwort ist bereits die akzeptierte Antwort. Ich füge hier etwas hinzu, das meiner Meinung nach sowohl lesbar als auch einfach und in diesem Fall offen für zukünftige Änderungen ist:

Beginnend mit der Antwort von @ZdeslavVojkovic (die ich ziemlich gut finde), kam ich auf Folgendes:

#include <iostream>
#include <set>

//using namespace std;

int GetScenarioInt(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    return bValue1 << 3 | bValue2 << 2 | bValue3 << 1 | bValue4;
}
bool IsValidScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    std::set<int> validScenarios;
    validScenarios.insert(GetScenarioInt(true, true, true, true));
    validScenarios.insert(GetScenarioInt(true, true, true, false));
    validScenarios.insert(GetScenarioInt(true, false, false, false));

    int currentScenario = GetScenarioInt(bValue1, bValue2, bValue3, bValue4);

    return validScenarios.find(currentScenario) != validScenarios.end();
}

int main()
{
    std::cout << IsValidScenario(true, true, true, false) << "\n"; // expected = true;
    std::cout << IsValidScenario(true, true, false, false) << "\n"; // expected = false;

    return 0;
}

Sehen Sie es bei der Arbeit hier

Nun, das ist die "elegante und wartbare" (IMHO) Lösung, die ich normalerweise anstrebe, aber für den OP-Fall passt meine vorherige "Bündel von Wenns" -Antwort besser zu den OP-Anforderungen, auch wenn sie weder elegant noch wartbar ist.

Gian Paolo
quelle
Sie wissen, dass Sie Ihre vorherige Antwort jederzeit bearbeiten und Verbesserungen vornehmen können.
Andreas
20

Ich möchte auch einen anderen Ansatz einreichen.

Meine Idee ist es, die Bools in eine Ganzzahl umzuwandeln und dann mit verschiedenen Vorlagen zu vergleichen:

unsigned bitmap_from_bools(bool b) {
    return b;
}
template<typename... args>
unsigned bitmap_from_bools(bool b, args... pack) {
    return (bitmap_from_bools(b) << sizeof...(pack)) | bitmap_from_bools(pack...);
}

int main() {
    bool bValue1;
    bool bValue2;
    bool bValue3;
    bool bValue4;

    unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);

    if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) {
        //bad scenario
    }
}

Beachten Sie, wie dieses System bis zu 32 Bools als Eingabe unterstützen kann. Das Ersetzen unsigneddurch unsigned long long(oder uint64_t) erhöht die Unterstützung auf 64 Fälle. Wenn Ihnen das nicht gefällt if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u), können Sie auch eine andere variable Vorlagenmethode verwenden:

bool equals_any(unsigned target, unsigned compare) {
    return target == compare;
}
template<typename... args>
bool equals_any(unsigned target, unsigned compare, args... compare_pack) {
    return equals_any(target, compare) ? true : equals_any(target, compare_pack...);
}

int main() {
    bool bValue1;
    bool bValue2;
    bool bValue3;
    bool bValue4;

    unsigned summary = bitmap_from_bools(bValue1, bValue2, bValue3, bValue4);

    if (!equals_any(summary, 0b1111u, 0b1110u, 0b1000u)) {
        //bad scenario
    }
}
Stapel Danny
quelle
2
Ich liebe diesen Ansatz, bis auf den Namen der Hauptfunktion: "Von Bool ... zu was ?" - Warum nicht explizit bitmap_from_boolsoder bools_to_bitmap?
Konrad Rudolph
ja @KonradRudolph, ich könnte mir keinen besseren Namen vorstellen, außer vielleicht bools_to_unsigned. Bitmap ist ein gutes Schlüsselwort. bearbeitet.
Stapeln Sie Danny
Ich denke du willst summary!= 0b1111u &&.... a != b || a != cist immer wahr, wennb != c
MooseBoys
17

Hier ist eine vereinfachte Version:

if (bValue1 && (bValue2 == bValue3) && (bValue2 || !bValue4)) {
    // acceptable
} else {
    // not acceptable
}

Beachten Sie natürlich, dass diese Lösung stärker verschleiert ist als die ursprüngliche. Ihre Bedeutung ist möglicherweise schwerer zu verstehen.


Update: MSalters in den Kommentaren fanden einen noch einfacheren Ausdruck:

if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...
geza
quelle
1
Ja, aber schwer zu verstehen. Aber danke für den Vorschlag.
Andrew Truckle
Ich habe die Fähigkeit von Compilern, den Ausdruck zu vereinfachen, mit Ihrer Vereinfachung als Referenz verglichen: Compiler-Explorer . gcc hat Ihre optimale Version nicht gefunden, aber die Lösung ist immer noch gut. Clang und MSVC scheinen keine Vereinfachung des booleschen Ausdrucks durchzuführen.
Oliv
1
@ AndrewTruckle: Beachten Sie, dass Sie dies bitte sagen, wenn Sie eine besser lesbare Version benötigen. Sie haben "vereinfacht" gesagt, akzeptieren jedoch eine noch ausführlichere Version als Ihre ursprüngliche.
Geza
1
simpleist in der Tat ein vager Begriff. Viele Leute verstehen es in diesem Zusammenhang als einfacher für Entwickler zu verstehen und nicht für den Compiler, Code zu generieren, so dass ausführlicher tatsächlich einfacher sein kann.
Zdeslav Vojkovic
1
@IsmaelMiguel: Wenn eine Logikformel für die Anzahl der Begriffe optimiert wird, geht normalerweise die ursprüngliche Bedeutung verloren. Aber man kann einen Kommentar dazu schreiben, so dass klar ist, was es tut. Selbst für die akzeptierte Antwort würde ein Kommentar nicht schaden.
Geza
12

Überlegen Sie, Ihre Tabellen so direkt wie möglich in Ihr Programm zu übersetzen. Fahren Sie das Programm von der Tabelle aus, anstatt es mit Logik nachzuahmen.

template<class T0>
auto is_any_of( T0 const& t0, std::initializer_list<T0> il ) {
  for (auto&& x:il)
    if (x==t0) return true;
  return false;
}

jetzt

if (is_any_of(
  std::make_tuple(bValue1, bValue2, bValue3, bValue4),
  {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false}
  }
))

Dadurch wird Ihre Wahrheitstabelle direkt wie möglich in den Compiler codiert.

Live-Beispiel .

Sie können auch std::any_ofdirekt verwenden:

using entry = std::array<bool, 4>;
constexpr entry acceptable[] = 
  {
    {true, true, true, true},
    {true, true, true, false},
    {true, false, false, false}
  };
if (std::any_of( begin(acceptable), end(acceptable), [&](auto&&x){
  return entry{bValue1, bValue2, bValue3, bValue4} == x;
}) {
}

Der Compiler kann den Code einbinden, jede Iteration eliminieren und eine eigene Logik für Sie erstellen. In der Zwischenzeit spiegelt Ihr Code genau wider, wie Sie das Problem erkannt haben.

Yakk - Adam Nevraumont
quelle
Die erste Version ist so einfach zu lesen und so wartbar, dass ich sie wirklich mag. Der zweite ist zumindest für mich schwerer zu lesen und erfordert ein C ++ - Niveau, das möglicherweise über dem Durchschnitt liegt, sicherlich über meinem. Nicht jeder kann schreiben. Ich habe gerade etwas Neues gelernt, danke
Gian Paolo
11

Ich gebe hier nur meine Antwort, wie in den Kommentaren, die jemand vorgeschlagen hat, um meine Lösung zu zeigen. Ich möchte allen für ihre Erkenntnisse danken.

Am Ende habe ich mich entschieden, drei neue "Szenario" boolean-Methoden hinzuzufügen :

bool CChristianLifeMinistryValidationDlg::IsFirstWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) && 
           !INCLUDE_ITEM2(pEntry) && 
           !INCLUDE_ITEM3(pEntry) && 
           !INCLUDE_ITEM4(pEntry));
}

bool CChristianLifeMinistryValidationDlg::IsSecondWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) &&
            INCLUDE_ITEM2(pEntry) &&
            INCLUDE_ITEM3(pEntry) &&
            INCLUDE_ITEM4(pEntry));
}

bool CChristianLifeMinistryValidationDlg::IsOtherWeekStudentItems(CChristianLifeMinistryEntry *pEntry)
{
    return (INCLUDE_ITEM1(pEntry) && 
            INCLUDE_ITEM2(pEntry) && 
            INCLUDE_ITEM3(pEntry) && 
           !INCLUDE_ITEM4(pEntry));
}

Dann konnte ich meine Validierungsroutine folgendermaßen anwenden:

if (!IsFirstWeekStudentItems(pEntry) && !IsSecondWeekStudentItems(pEntry) && !IsOtherWeekStudentItems(pEntry))
{
    ; Error
}

In meiner Live-Anwendung werden die 4 Bool-Werte tatsächlich aus einem extrahiert, in DWORDdem 4 Werte codiert sind.

Nochmals vielen Dank an alle.

Andrew Truckle
quelle
1
Vielen Dank, dass Sie die Lösung geteilt haben. :) Es ist eigentlich besser als der Komplex, wenn die Bedingungen Hölle. Vielleicht können Sie INCLUDE_ITEM1usw. noch besser benennen und Sie sind alle gut. :)
Hardik Modha
1
@HardikModha Nun, technisch gesehen handelt es sich um "Schülergegenstände", und die Flagge soll anzeigen, ob sie "enthalten" sein sollen. Daher denke ich, dass der Name, obwohl er generisch klingt, in diesem Zusammenhang tatsächlich von Bedeutung ist. :)
Andrew Truckle
11

Ich sehe keine Antworten, um die Szenarien zu benennen, obwohl die Lösung des OP genau das tut.

Für mich ist es am besten, den Kommentar jedes Szenarios entweder in einen Variablennamen oder einen Funktionsnamen zu kapseln. Es ist wahrscheinlicher, dass Sie einen Kommentar als einen Namen ignorieren. Wenn sich Ihre Logik in Zukunft ändert, ändern Sie eher einen Namen als einen Kommentar. Sie können einen Kommentar nicht umgestalten.

Wenn Sie diese Szenarien außerhalb Ihrer Funktion wiederverwenden möchten (oder möchten), erstellen Sie eine Funktion, die angibt, was ausgewertet wird ( constexpr/ noexceptoptional, aber empfohlen):

constexpr bool IsScenario1(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && b4; }

constexpr bool IsScenario2(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && b2 && b3 && !b4; }

constexpr bool IsScenario3(bool b1, bool b2, bool b3, bool b4) noexcept
{ return b1 && !b2 && !b3 && !b4; }

Machen Sie diese Klassenmethoden wenn möglich (wie in der OP-Lösung). Sie können Variablen innerhalb Ihrer Funktion verwenden, wenn Sie nicht glauben, dass Sie die Logik wiederverwenden werden:

const auto is_scenario_1 = bValue1 && bValue2 && bValue3 && bValue4;
const auto is_scenario_2 = bvalue1 && bvalue2 && bValue3 && !bValue4;
const auto is_scenario_3 = bValue1 && !bValue2 && !bValue3 && !bValue4;

Der Compiler wird höchstwahrscheinlich feststellen, dass alle Szenarien falsch sind, wenn bValue1 falsch ist. Mach dir keine Sorgen, dass es schnell geht, nur korrekt und lesbar. Wenn Sie Ihren Code profilieren und feststellen, dass dies ein Engpass ist, weil der Compiler bei -O2 oder höher suboptimalen Code generiert hat, versuchen Sie, ihn neu zu schreiben.

Fehlerhaft
quelle
Ich mag dies etwas mehr als die (bereits nette) Lösung von Gian Paolo: Sie vermeidet den Kontrollfluss und die Verwendung einer überschriebenen Variablen - mehr funktionalen Stil.
Dirk Herrmann
9

AC / C ++ Weg

bool scenario[3][4] = {{true, true, true, true}, 
                        {true, true, true, false}, 
                        {true, false, false, false}};

bool CheckScenario(bool bValue1, bool bValue2, bool bValue3, bool bValue4)
{
    bool temp[] = {bValue1, bValue2, bValue3, bValue4};
    for(int i = 0 ; i < sizeof(scenario) / sizeof(scenario[0]); i++)
    {
        if(memcmp(temp, scenario[i], sizeof(temp)) == 0)
            return true;
    }
    return false;
}

Dieser Ansatz ist skalierbar, als ob die Anzahl der gültigen Bedingungen zunimmt. Sie können einfach weitere davon zur Szenarioliste hinzufügen.

hessam hedieh
quelle
Ich bin mir jedoch ziemlich sicher, dass dies falsch ist. Es wird davon ausgegangen, dass der Compiler nur eine einzige binäre Darstellung für verwendet true. Ein Compiler, der "alles, was nicht Null ist, ist wahr" verwendet, führt dazu, dass dieser Code fehlschlägt. Beachten Sie, dass truemuss konvertieren zu 1, es muss einfach nicht sein gespeichert als solche.
MSalters
@MSalters, tnx, ich verstehe Ihren Standpunkt und mir ist bewusst, dass 2 is not equal to true but evaluates to truemein Code nicht erzwingt int 1 = trueund funktioniert, solange alle Wahrheiten in denselben int-Wert konvertiert werden. Hier ist meine Frage: Warum sollte der Compiler beim Konvertieren zufällig handeln? Getreu dem zugrunde liegenden Int. Können Sie bitte mehr näher darauf eingehen?
Hessam Hedieh
Das Durchführen von a memcmpzum Testen von booleschen Bedingungen ist nicht die C ++ - Methode, und ich bezweifle eher, dass es sich auch um eine etablierte C-Methode handelt.
Konrad Rudolph
@hessamhedieh: Das Problem in Ihrer Logik ist "Konvertieren von true in zugrunde liegendes int". So funktionieren Compiler nicht
MSalters
Ihr Code erhöht die Komplexität von O (1) auf O (n). Kein Weg in eine Sprache - lassen Sie C / C ++ beiseite.
Mabel
9

Es ist leicht zu bemerken, dass die ersten beiden Szenarien ähnlich sind - sie teilen die meisten Bedingungen. Wenn Sie auswählen möchten, in welchem ​​Szenario Sie sich gerade befinden, können Sie es folgendermaßen schreiben (es handelt sich um eine modifizierte @ gian-paolo -Lösung):

bool valid = false;
if(bValue1 && bValue2 && bValue3)
{
    if (bValue4)
        valid = true; //scenario 1
    else if (!bValue4)
        valid = true; //scenario 2
}
else if (bValue1 && !bValue2 && !bValue3 && !bValue4)
    valid = true; //scenario 3

Wenn Sie weiter gehen, können Sie feststellen, dass der erste Boolesche Wert immer wahr sein muss. Dies ist eine Eingabebedingung, sodass Sie am Ende Folgendes erhalten können:

bool valid = false;
if(bValue1)
{
    if(bValue2 && bValue3)
    {
        if (bValue4)
            valid = true; //scenario 1
        else if (!bValue4)
            valid = true; //scenario 2
    }
    else if (!bValue2 && !bValue3 && !bValue4)
        valid = true; //scenario 3
}

Darüber hinaus können Sie jetzt deutlich erkennen, dass bValue2 und bValue3 in gewisser Weise miteinander verbunden sind. Sie können ihren Status in einige externe Funktionen oder Variablen mit einem passenderen Namen extrahieren (dies ist jedoch nicht immer einfach oder angemessen):

bool valid = false;
if(bValue1)
{
    bool bValue1and2 = bValue1 && bValue2;
    bool notBValue1and2 = !bValue2 && !bValue3;
    if(bValue1and2)
    {
        if (bValue4)
            valid = true; //scenario 1
        else if (!bValue4)
            valid = true; //scenario 2
    }
    else if (notBValue1and2 && !bValue4)
        valid = true; //scenario 3
}

Dies zu tun hat einige Vor- und Nachteile:

  • Die Bedingungen sind kleiner, daher ist es einfacher, darüber nachzudenken.
  • Es ist einfacher, eine nette Umbenennung vorzunehmen, um diese Bedingungen verständlicher zu machen.
  • aber sie müssen den Umfang verstehen,
  • außerdem ist es starrer

Wenn Sie vorhersagen, dass sich Änderungen an der obigen Logik ergeben werden, sollten Sie einen einfacheren Ansatz verwenden, wie er von @ gian-paolo dargestellt wird .

Andernfalls sollten Sie mein letztes Code-Snippet in Betracht ziehen, wenn diese Bedingungen gut etabliert sind und eine Art "solide Regeln" darstellen, die sich nie ändern werden.

Michał Łoś
quelle
7

Wie von mch vorgeschlagen, können Sie Folgendes tun:

if(!((bValue1 && bValue2 && bValue3) || 
  (bValue1 && !bValue2 && !bValue3 && !bValue4))
)

Dabei deckt die erste Zeile die beiden ersten guten Fälle ab und die zweite Zeile den letzten.

Live Demo, wo ich herumgespielt habe und es deine Fälle passiert.

gsamaras
quelle
7

Eine kleine Variation von @ GianPaolos feiner Antwort, die einige vielleicht leichter zu lesen finden:

bool any_of_three_scenarios(bool v1, bool v2, bool v3, bool v4)
{
  return (v1 &&  v2 &&  v3 &&  v4)  // scenario 1
      || (v1 &&  v2 &&  v3 && !v4)  // scenario 2
      || (v1 && !v2 && !v3 && !v4); // scenario 3
}

if (any_of_three_scenarios(bValue1,bValue2,bValue3,bValue4))
{
  // ...
}
Matt
quelle
7

Jede Antwort ist zu komplex und schwer zu lesen. Die beste Lösung hierfür ist eine switch()Aussage. Es ist sowohl lesbar als auch erleichtert das Hinzufügen / Ändern zusätzlicher Fälle. Compiler sind auch gut darin, switch()Anweisungen zu optimieren .

switch( (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1) )
{
    case 0b1111:
        // scenario 1
        break;

    case 0b0111:
        // scenario 2
        break;

    case 0b0001:
        // scenario 3
        break;

    default:
        // fault condition
        break;
}

Sie können natürlich Konstanten und ODER-Verknüpfungen in den caseAnweisungen verwenden, um die Lesbarkeit zu verbessern.

beschlagen
quelle
Als alter C-Programmierer würde ich ein "PackBools" -Makro definieren und dieses sowohl für den "Schalter (PackBools (a, b, c, d))" als auch für die Fälle verwenden, z. B. entweder direkt "case PackBools (true) , true ...) "oder definieren Sie sie als lokale constants.eg" const unsigned int szenario1 = PackBools (true, true ...); "
Simon F
6

Aus Gründen der Übersichtlichkeit würde ich auch Verknüpfungsvariablen verwenden. Wie bereits erwähnt, entspricht Szenario 1 Szenario 2, da der Wert von bValue4 die Wahrheit dieser beiden Szenarien nicht beeinflusst.

bool MAJORLY_TRUE=bValue1 && bValue2 && bValue3
bool MAJORLY_FALSE=!(bValue2 || bValue3 || bValue4)

dann wird dein Ausdruck:

if (MAJORLY_TRUE || (bValue1 && MAJORLY_FALSE))
{
     // do something
}
else
{
    // There is some error
}

Das Vergeben aussagekräftiger Namen für MAJORTRUE- und MAJORFALSE-Variablen (sowie für bValue * -Vars) würde die Lesbarkeit und Wartung erheblich verbessern.

Gnudiff
quelle
6

Konzentrieren Sie sich auf die Lesbarkeit des Problems, nicht auf die spezifische "if" -Anweisung.

Dies führt zwar zu mehr Codezeilen, und einige halten dies möglicherweise für übertrieben oder unnötig. Ich würde vorschlagen, dass das Abstrahieren Ihrer Szenarien von den spezifischen Booleschen Werten der beste Weg ist, um die Lesbarkeit aufrechtzuerhalten.

Durch die Aufteilung der Dinge in Klassen (Sie können einfach nur Funktionen oder ein anderes von Ihnen bevorzugtes Tool verwenden) mit verständlichen Namen können wir die Bedeutung hinter jedem Szenario viel einfacher aufzeigen. Noch wichtiger ist, dass es in einem System mit vielen beweglichen Teilen einfacher ist, Ihre vorhandenen Systeme zu warten und mit ihnen zu verbinden (auch wenn so viel zusätzlicher Code erforderlich ist).

#include <iostream>
#include <vector>
using namespace std;

// These values would likely not come from a single struct in real life
// Instead, they may be references to other booleans in other systems
struct Values
{
    bool bValue1; // These would be given better names in reality
    bool bValue2; // e.g. bDidTheCarCatchFire
    bool bValue3; // and bDidTheWindshieldFallOff
    bool bValue4;
};

class Scenario
{
public:
    Scenario(Values& values)
    : mValues(values) {}

    virtual operator bool() = 0;

protected:
    Values& mValues;    
};

// Names as examples of things that describe your "scenarios" more effectively
class Scenario1_TheCarWasNotDamagedAtAll : public Scenario
{
public:
    Scenario1_TheCarWasNotDamagedAtAll(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && mValues.bValue2
        && mValues.bValue3
        && mValues.bValue4;
    }
};

class Scenario2_TheCarBreaksDownButDidntGoOnFire : public Scenario
{
public:
    Scenario2_TheCarBreaksDownButDidntGoOnFire(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && mValues.bValue2
        && mValues.bValue3
        && !mValues.bValue4;
    }   
};

class Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere : public Scenario
{
public:
    Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(Values& values) : Scenario(values) {}

    virtual operator bool()
    {
        return mValues.bValue1
        && !mValues.bValue2
        && !mValues.bValue3
        && !mValues.bValue4;
    }   
};

Scenario* findMatchingScenario(std::vector<Scenario*>& scenarios)
{
    for(std::vector<Scenario*>::iterator it = scenarios.begin(); it != scenarios.end(); it++)
    {
        if (**it)
        {
            return *it;
        }
    }
    return NULL;
}

int main() {
    Values values = {true, true, true, true};
    std::vector<Scenario*> scenarios = {
        new Scenario1_TheCarWasNotDamagedAtAll(values),
        new Scenario2_TheCarBreaksDownButDidntGoOnFire(values),
        new Scenario3_TheCarWasCompletelyWreckedAndFireEverywhere(values)
    };

    Scenario* matchingScenario = findMatchingScenario(scenarios);

    if(matchingScenario)
    {
        std::cout << matchingScenario << " was a match" << std::endl;
    }
    else
    {
        std::cout << "No match" << std::endl;
    }

    // your code goes here
    return 0;
}

quelle
5
Irgendwann beeinträchtigt die Ausführlichkeit die Lesbarkeit. Ich denke das geht zu weit.
JollyJoker
2
@JollyJoker Ich stimme in dieser speziellen Situation tatsächlich zu - mein Bauchgefühl aufgrund der Art und Weise, wie OP alles extrem allgemein benannt hat, ist jedoch, dass ihr "echter" Code wahrscheinlich viel komplexer ist als das Beispiel, das sie gegeben haben. Wirklich, ich wollte diese Alternative nur herausbringen, da ich sie so für etwas viel Komplexeres / Beteiligteres strukturieren würde. Aber Sie haben Recht - für OP-spezifische Beispiele ist es zu ausführlich und macht die Sache noch schlimmer.
5

Es kommt darauf an, was sie darstellen.

Wenn beispielsweise 1 ein Schlüssel ist und 2 und 3 zwei Personen sind, die zustimmen müssen (es sei denn, sie sind sich einig, dass NOTsie eine dritte Person benötigen - 4 -, um dies zu bestätigen), ist möglicherweise am besten lesbar:

1 &&
    (
        (2 && 3)   
        || 
        ((!2 && !3) && !4)
    )

auf vielfachen Wunsch:

Key &&
    (
        (Alice && Bob)   
        || 
        ((!Alice && !Bob) && !Charlie)
    )
ispiro
quelle
2
Sie haben vielleicht Recht, aber die Verwendung von Zahlen zur Veranschaulichung Ihres Punktes beeinträchtigt Ihre Antwort. Versuchen Sie es mit beschreibenden Namen.
jxh
1
@jxh Das sind die verwendeten OP-Nummern. Ich habe gerade die entfernt bValue.
Ispiro
@jxh Ich hoffe es ist jetzt besser.
Ispiro
4

Die bitweise Bedienung sieht sehr sauber und verständlich aus.

int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
    //satisfying condition
}
Derviş Kayımbaşıoğlu
quelle
1
Der bitweise Vergleich erscheint mir lesbar. Die Komposition hingegen sieht künstlich aus.
xtofl
3

Ich bezeichne a, b, c, d aus Gründen der Klarheit und A, B, C, D für Ergänzungen

bValue1 = a (!A)
bValue2 = b (!B)
bValue3 = c (!C)
bValue4 = d (!D)

Gleichung

1 = abcd + abcD + aBCD
  = a (bcd + bcD + BCD)
  = a (bc + BCD)
  = a (bcd + D (b ^C))

Verwenden Sie alle Gleichungen, die zu Ihnen passen.

mabel
quelle
3
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1 muss immer wahr sein
  • b2 muss immer gleich b3 sein
  • und b4 kann nicht falsch sein, wenn b2 (und b3) wahr sind

einfach

Owen Meyer
quelle
3

Nur eine persönliche Präferenz gegenüber der akzeptierten Antwort, aber ich würde schreiben:

bool valid = false;
// scenario 1
valid = valid || (bValue1 && bValue2 && bValue3 && bValue4);
// scenario 2
valid = valid || (bValue1 && bValue2 && bValue3 && !bValue4);
// scenario 3
valid = valid || (bValue1 && !bValue2 && !bValue3 && !bValue4);
François Gueguen
quelle
2

Angenommen, Sie können nur die Szenarioprüfung ändern, würde ich mich auf die Lesbarkeit konzentrieren und die Prüfung einfach in eine Funktion einbinden, damit Sie sie einfach aufrufen können if(ScenarioA()).


Angenommen, Sie möchten / müssen dies tatsächlich optimieren, würde ich empfehlen, die eng verknüpften Booleschen Werte in konstante Ganzzahlen umzuwandeln und Bitoperatoren für sie zu verwenden

public class Options {
  public const bool A = 2; // 0001
  public const bool B = 4; // 0010
  public const bool C = 16;// 0100
  public const bool D = 32;// 1000
//public const bool N = 2^n; (up to n=32)
}

...

public isScenario3(int options) {
  int s3 = Options.A | Options.B | Options.C;
  // for true if only s3 options are set
  return options == s3;
  // for true if s3 options are set
  // return options & s3 == s3
}

Dies macht das Ausdrücken der Szenarien so einfach wie das Auflisten der Teile, ermöglicht es Ihnen, mit einer switch-Anweisung zum richtigen Zustand zu springen und andere Entwickler zu verwirren, die dies noch nicht gesehen haben. (C # RegexOptions verwendet dieses Muster zum Setzen von Flags. Ich weiß nicht, ob es ein Beispiel für eine C ++ - Bibliothek gibt.)

Tezra
quelle
Tatsächlich verwende ich nicht vier Bool-Werte, sondern ein DWORD mit vier eingebetteten BOOLS. Zu spät, um es jetzt zu ändern. Aber danke für deinen Vorschlag.
Andrew Truckle
2

Verschachtelte ifs könnten für manche Menschen leichter zu lesen sein. Hier ist meine Version

bool check(int bValue1, int bValue2, int bValue3, int bValue4)
{
  if (bValue1)
  {
    if (bValue2)
    {
      // scenario 1-2
      return bValue3;
    }
    else
    {
      // scenario 3
      return !bValue3 && !bValue4;
    }
  }

  return false;
}
Sardok
quelle
Persönlich würde ich es normalerweise vermeiden, if-Anweisungen zu verschachteln, wenn dies möglich ist. Obwohl dieser Fall nett und lesbar ist, kann die Verschachtelung sehr schwer zu lesen sein, sobald neue Möglichkeiten hinzugefügt werden. Aber wenn sich die Szenarien nie ändern, ist es definitiv eine schöne und lesbare Lösung.
Dnomyar96
@ Dnomyar96 Ich stimme zu. Ich persönlich vermeide auch verschachtelte Wenns. Manchmal, wenn die Logik kompliziert ist, fällt es mir leichter, die Logik zu verstehen, indem ich sie in Teile zerlege. Wenn Sie beispielsweise bValue1Block eingeben , können Sie alles darin als eine neue frische Seite in Ihrem mentalen Prozess behandeln. Ich wette, die Art und Weise, wie man sich dem Problem nähert, kann sehr persönlich oder sogar kulturell sein.
Sardok
1

Auf diese Frage wurden mehrere richtige Antworten gegeben, aber ich würde eine andere Ansicht vertreten: Wenn der Code zu kompliziert aussieht, stimmt etwas nicht ganz . Der Code ist schwer zu debuggen und eher "nur zur einmaligen Verwendung".

Wenn wir im wirklichen Leben eine Situation wie diese finden:

         Scenario 1 | Scenario 2 | Scenario 3
bValue1: true       | true       | true
bValue2: true       | true       | false
bValue3: true       | true       | false
bValue4: true       | false      | false

Wenn vier Zustände durch ein so genaues Muster verbunden sind, haben wir es mit der Konfiguration einer "Entität" in unserem Modell zu tun .

Eine extreme Metapher ist, wie wir einen "Menschen" in einem Modell beschreiben würden, wenn wir uns ihrer Existenz als einheitliche Einheiten mit Komponenten, die in bestimmten Freiheitsgraden verbunden sind, nicht bewusst wären: Wir müssten unabhängige Zustände von "Torsos" beschreiben. "Arme", "Beine" und "Kopf", was es kompliziert machen würde, das beschriebene System zu verstehen. Ein unmittelbares Ergebnis wären unnatürlich komplizierte boolesche Ausdrücke.

Offensichtlich ist der Weg zur Reduzierung der Komplexität die Abstraktion, und ein Werkzeug der Wahl in c ++ ist das Objektparadigma .

Die Frage ist also: warum gibt es so ein Muster? Was ist das und was repräsentiert es?

Da wir die Antwort nicht kennen, können wir auf eine mathematische Abstraktion zurückgreifen: das Array : Wir haben drei Szenarien, von denen jedes jetzt ein Array ist.

                0   1   2   3
Scenario 1:     T   T   T   T
Scenario 2:     T   T   T   F
Scenario 3:     T   F   F   F

An diesem Punkt haben Sie Ihre Erstkonfiguration. als Array. Z.Bstd::array hat einen Gleichheitsoperator:

An welchem ​​Punkt wird Ihre Syntax:

if( myarray == scenario1 ) {
  // arrays contents are the same

} 
else if ( myarray == scenario2 ) {
  // arrays contents are the same

} 

else if ( myarray == scenario3 ) {
  // arrays contents are the same

} 
else {
  // not the same

}

Genau wie die Antwort von Gian Paolo ist sie kurz, klar und leicht zu überprüfen / zu debuggen. In diesem Fall haben wir die Details der booleschen Ausdrücke an den Compiler delegiert.

fralau
quelle
1

Sie müssen sich keine Gedanken über ungültige Kombinationen von Booleschen Flags machen, wenn Sie die Booleschen Flags entfernen.

Die akzeptablen Werte sind:

         Scenario 1 | Scenario 2 | Scenario 3
bValue1: true       | true       | true
bValue2: true       | true       | false
bValue3: true       | true       | false
bValue4: true       | false      | false

Sie haben eindeutig drei Zustände (Szenarien). Es wäre besser, dies zu modellieren und die booleschen Eigenschaften aus diesen Zuständen abzuleiten , nicht umgekehrt.

enum State
{
    scenario1,
    scenario2,
    scenario3,
};

inline bool isValue1(State s)
{
    // (Well, this is kind of silly.  Do you really need this flag?)
    return true;
}

inline bool isValue2(State s)
{
    switch (s)
    {
        case scenario1:
        case scenario2:
            return true;
        case scenario3:
            return false;
    }
}

inline bool isValue3(State s)
{
    // (This is silly too.  Do you really need this flag?)
    return isValue2(s);
}

inline bool isValue4(State s)
{
    switch (s)
    {
        case scenario1:
            return true;
        case scenario2:
        case scenario3:
            return false;
    }
}

Dies ist definitiv mehr Code als in Gian Paolos Antwort , aber abhängig von Ihrer Situation könnte dies viel wartbarer sein:

  • Es gibt eine zentrale Reihe von Funktionen, die geändert werden müssen, wenn zusätzliche boolesche Eigenschaften oder Szenarien hinzugefügt werden.
    • Zum Hinzufügen von Eigenschaften muss nur eine einzige Funktion hinzugefügt werden.
    • Wenn Sie ein Szenario hinzufügen und Compiler-Warnungen über nicht behandelte enumFälle in switchAnweisungen aktivieren , werden Eigenschafts-Getter abgefangen , die dieses Szenario nicht behandeln.
  • Wenn Sie die booleschen Eigenschaften dynamisch ändern müssen, müssen Sie ihre Kombinationen nicht überall erneut validieren. Anstatt einzelne boolesche Flags umzuschalten (was zu ungültigen Kombinationen von Flags führen könnte), hätten Sie stattdessen eine Zustandsmaschine, die von einem Szenario in ein anderes übergeht.

Dieser Ansatz hat auch den Nebeneffekt, sehr effizient zu sein.

Jamesdlin
quelle
0

Meine 2 Cent: deklarieren Sie eine variable Summe (Integer) damit

if(bValue1)
{
  sum=sum+1;
}
if(bValue2)
{
  sum=sum+2;
}
if(bValue3)
{
  sum=sum+4;
}
if(bValue4)
{
  sum=sum+8;
}

Prüfen Sie die Summe anhand der gewünschten Bedingungen und fertig. Auf diese Weise können Sie in Zukunft problemlos weitere Bedingungen hinzufügen, sodass das Lesen recht einfach ist.

SCdev
quelle
0

Die akzeptierte Antwort ist in Ordnung, wenn Sie nur 3 Fälle haben und die Logik für jeden einfach ist.

Wenn die Logik für jeden Fall komplizierter wäre oder es viel mehr Fälle gibt, ist es weitaus besser, das Entwurfsmuster der Verantwortungskette zu verwenden.

Sie erstellen eine, BaseValidatordie einen Verweis auf a BaseValidatorund eine Methode auf validateund eine Methode zum Aufrufen der Validierung für den referenzierten Validator enthält.

class BaseValidator {
    BaseValidator* nextValidator;

    public:
    BaseValidator() {
        nextValidator = 0;
    }

    void link(BaseValidator validator) {
        if (nextValidator) {
            nextValidator->link(validator);
        } else {
            nextValidator = validator;
        }
    }

    bool callLinkedValidator(bool v1, bool v2, bool v3, bool v4) {
        if (nextValidator) {
            return nextValidator->validate(v1, v2, v3, v4);
        }

        return false;
    }

    virtual bool validate(bool v1, bool v2, bool v3, bool v4) {
        return false;
    }
}

Anschließend erstellen Sie eine Reihe von Unterklassen, die von der erben BaseValidator, und überschreiben die validateMethode mit der für jeden Validator erforderlichen Logik.

class Validator1: public BaseValidator {
    public:
    bool validate(bool v1, bool v2, bool v3, bool v4) {
        if (v1 && v2 && v3 && v4) {
            return true;
        }

        return nextValidator->callLinkedValidator(v1, v2, v3, v4);
    }
}

Dann ist es einfach, jeden Ihrer Validatoren zu instanziieren und jeden von ihnen als Wurzel der anderen festzulegen:

Validator1 firstValidator = new Validator1();
Validator2 secondValidator = new Validator2();
Validator3 thirdValidator = new Validator3();
firstValidator.link(secondValidator);
firstValidator.link(thirdValidator);
if (firstValidator.validate(value1, value2, value3, value4)) { ... }

Im Wesentlichen hat jeder Validierungsfall eine eigene Klasse, die dafür verantwortlich ist, (a) festzustellen, ob die Validierung mit dieser übereinstimmt Fall , und (b) die Validierung an eine andere Person in der Kette zu senden, wenn dies nicht der Fall ist.

Bitte beachten Sie, dass ich mit C ++ nicht vertraut bin. Ich habe versucht, die Syntax einiger Beispiele zu finden, die ich online gefunden habe. Wenn dies jedoch nicht funktioniert, behandeln Sie sie eher wie einen Pseudocode. Ich habe auch ein vollständiges funktionierendes Python-Beispiel unten, das bei Bedarf als Grundlage verwendet werden kann.

class BaseValidator:
    def __init__(self):
        self.nextValidator = 0

    def link(self, validator):
        if (self.nextValidator):
            self.nextValidator.link(validator)
        else:
            self.nextValidator = validator

    def callLinkedValidator(self, v1, v2, v3, v4):
        if (self.nextValidator):
            return self.nextValidator.validate(v1, v2, v3, v4)

        return False

    def validate(self, v1, v2, v3, v4):
        return False

class Validator1(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator2(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and v2 and v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

class Validator3(BaseValidator):
    def validate(self, v1, v2, v3, v4):
        if (v1 and not v2 and not v3 and not v4):
            return True
        return self.callLinkedValidator(v1, v2, v3, v4)

firstValidator = Validator1()
secondValidator = Validator2()
thirdValidator = Validator3()
firstValidator.link(secondValidator)
firstValidator.link(thirdValidator)
print(firstValidator.validate(False, False, True, False))

Auch hier finden Sie diesen Overkill möglicherweise für Ihr spezielles Beispiel, aber er erzeugt viel saubereren Code, wenn Sie am Ende eine weitaus kompliziertere Reihe von Fällen haben, die erfüllt werden müssen.

Jim Cullen
quelle
-2

Ein einfacher Ansatz besteht darin, die Antwort zu finden, die Sie für akzeptabel halten.

Ja = (boolean1 && boolean2 && boolean3 && boolean4) + + ...

Vereinfachen Sie nun nach Möglichkeit die Gleichung mit der Booleschen Algebra.

wie in diesem Fall kombinieren sich akzeptabel1 und 2 zu (boolean1 && boolean2 && boolean3).

Daher lautet die endgültige Antwort:

(boolean1 && boolean2 && boolean3) || 
((boolean1 && !boolean2 && !boolean3 && !boolean4)
Rupesh
quelle
-3

verwenden Bit - Feld :

unoin {
  struct {
    bool b1: 1;
    bool b2: 1;
    bool b3: 1;
    bool b4: 1;
  } b;
  int i;
} u;

// set:
u.b.b1=true;
...

// test
if (u.i == 0x0f) {...}
if (u.i == 0x0e) {...}
if (u.i == 0x08) {...}

PS :

Das ist sehr schade für CPPer. Aber UB ist nicht meine Sorge, überprüfen Sie es unter http://coliru.stacked-crooked.com/a/2b556abfc28574a1 .

Hedzr
quelle
2
Dies führt dazu, dass UB auf ein inaktives Vereinigungsfeld zugreift.
HolyBlackCat
Formal ist es UB in C ++, Sie können nicht ein Gewerkschaftsmitglied festlegen und von einem anderen lesen. Technisch könnte es besser sein, Templated Getter \ Setter für Bits mit ganzzahligem Wert zu implementieren.
Swift - Friday Pie
Ich denke, das Verhalten würde sich auf Implementation-Defined ändern, wenn man die Adresse der Gewerkschaft in eine umwandeln würde unsigned char*, obwohl ich denke, dass ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1es wahrscheinlich effizienter wäre , einfach so etwas zu verwenden.
Supercat