wake-up-neo.com

Wie kann die Logik verbessert werden, um zu überprüfen, ob 4 boolesche Werte mit einigen Fällen übereinstimmen

Ich habe vier bool-Werte:

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

Dieses Szenario ist beispielsweise nicht akzeptabel:

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

Im Moment bin ich mit dieser if-Anweisung gekommen, 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?

115
Andrew Truckle

Ich würde die Lesbarkeit anstreben: Sie haben nur 3 Szenarien, behandeln 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 whichScenario zuweisen, während Sie mit der if fortfahren.

Mit nur 3 Szenarien würde ich nicht auf etwas wie "Wenn die ersten 3 Werte zutreffen, kann ich den vierten Wert überprüfen" verwenden: Es wird Ihren Code schwerer zu lesen und zu verwalten machen.

Keine elegante Lösung könnte sein sicher, aber in diesem fall ist ok: einfach und lesbar.

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

Ich liebe den ersten Vorschlag in diese Antwort : einfach zu lesen, nicht fehleranfällig, wartbar

(Fast) off topic:

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

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

196
Gian Paolo

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

Wichtig ist hier keine Prädikatenlogik. Es beschreibt Ihre Domain und bringt Ihre Absicht klar zum Ausdruck. Der Schlüssel hier ist, allen Eingaben und Zwischenvariablen gute Namen zu geben. Wenn Sie keine guten Variablennamen finden können, kann dies ein Zeichen dafür sein, dass Sie das Problem falsch beschreiben.

121
Anders

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

 enter image description here

Dies ergibt: 

 enter image description here

Wenn Sie A, B, C, D in bValue1, bValue2, bValue3, bValue4 ändern, ist dies nichts anderes als: 

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

Ihre if-Anweisung wird also zu:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Karnaugh Maps sind besonders nützlich, wenn Sie viele Variablen und viele Bedingungen haben, die true auswerten sollten. 
  • Nach dem Reduzieren der true-Szenarien auf eine logische Gleichung ist es sinnvoll, relevante Kommentare hinzuzufügen, die die true-Szenarien angeben.
104
P.W

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

Ich würde vorschlagen, dies als Bitflags 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 Flags. Die Unterstützung eines neuen Szenarios erfordert nur eine weitere Zeile in der Tabelle.

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");
}
58

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 für zukünftige Modifikationen offen ist:

Ausgehend von der Antwort von @ZdeslavVojkovic (die ich ziemlich gut finde) habe ich folgendes gefunden:

#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 wartungsfähige" (IMHO) Lösung, die ich normalerweise anstrebe, aber für den OP-Fall passt meine bisherige "Reihe von ifs" Antworten besser zu den OP-Anforderungen, auch wenn sie weder elegant noch wartungsfähig ist.

28
Gian Paolo

Ich möchte auch einen anderen Ansatz einreichen.

Meine Idee ist, die Bools in eine Ganzzahl umzuwandeln und dann mit variadischen 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, dass dieses System bis zu 32 Bools als Eingabe unterstützen kann. Durch Ersetzen von unsigned durch unsigned long long (oder uint64_t) wird die Unterstützung auf 64 Fälle erhöht. Wenn Sie if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u) nicht mögen, können Sie auch eine andere variadische 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
    }
}
19
Stack Danny

Hier ist eine vereinfachte Version:

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

Beachten Sie, dass diese Lösung natürlich mehr verschleiert als die ursprüngliche Lösung ist und ihre Bedeutung möglicherweise schwerer zu verstehen ist.


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

if (bValue1&&(bValue2==bValue3)&&(bValue2>=bValue4)) ...
16
geza

Erwägen Sie, Ihre Tabellen so direkt wie möglich in Ihr Programm zu übersetzen. Treiben Sie das Programm von der Tabelle aus, anstatt es mit Logik zu simulieren.

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

diese codiert Ihre Wahrheitstabelle direkt als möglich in den Compiler.

Live-Beispiel .

Sie können auch std::any_of direkt 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 integrieren, jede Iteration eliminieren und eine eigene Logik für Sie erstellen. Inzwischen spiegelt Ihr Code genau wider, wie Sie sich des Problems bewusst waren.

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

Am Ende habe ich drei neue "Szenario" boolean-Methoden hinzugefügt:

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 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 DWORD extrahiert, in das 4 Werte codiert sind.

Nochmals vielen Dank.

11
Andrew Truckle

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 in einen Variablennamen oder einen Funktionsnamen zu kapseln. Sie ignorieren eher einen Kommentar als einen Namen, und wenn sich Ihre Logik in der Zukunft ändert, ändern Sie wahrscheinlich eher einen Namen als einen Kommentar. Sie können einen Kommentar nicht umwandeln.

Wenn Sie beabsichtigen, diese Szenarien außerhalb Ihrer Funktion erneut zu verwenden (oder möchten), erstellen Sie eine Funktion, die angibt, was sie bewertet ( constexpr / noexcept optional, jedoch 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. Machen Sie sich keine Sorgen darüber, dass es schnell geht, nur richtig und lesbar. Wenn Sie Ihren Code profilieren und dies als Engpass empfinden, weil der Compiler bei -O2 oder höher suboptimalen Code generiert hat, versuchen Sie, ihn neu zu schreiben.

11
Erroneous

Ein C/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 lässt sich so skalieren, dass die Anzahl der gültigen Bedingungen zunimmt und Sie einfach weitere zur Szenarioliste hinzufügen. 

9
hessam hedieh

Es ist leicht zu bemerken, dass die ersten beiden Szenarien ähnlich sind - sie haben die meisten Bedingungen. Wenn Sie auswählen möchten, in welchem ​​Szenario Sie sich gerade befinden, können Sie es so schreiben (es ist 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 Eintrittsbedingung, sodass Sie 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
}

Außerdem können Sie jetzt deutlich erkennen, dass bValue2 und bValue3 etwas miteinander verbunden sind - Sie könnten ihren Status in einige externe Funktionen oder Variablen mit passenderem 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
}

Auf diese Weise zu haben, hat einige Vor- und Nachteile:

  • die Bedingungen sind kleiner, so dass es einfacher ist, über sie nachzudenken,
  • es ist einfacher, Nizza umzubenennen, um diese Bedingungen verständlicher zu machen.
  • aber sie müssen den Umfang verstehen, 
  • außerdem ist es starrer

Wenn Sie vorhersagen, dass Änderungen an der oben genannten Logik vorgenommen werden, sollten Sie einen direkteren Ansatz verwenden, wie von @ gian-paolo dargestellt.

Ansonsten, wenn diese Bedingungen gut etabliert sind und eine Art "fester Regeln" sind, die sich niemals ändern werden, bedenke ich mein letztes Code-Snippet.

9
Michał Łoś

Jede Antwort ist zu komplex und schwer lesbar. Die beste Lösung hierfür ist eine switch()-Anweisung. Es ist lesbar und vereinfacht das Hinzufügen/Ändern zusätzlicher Fälle. Compiler sind auch in der Lage, 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 OR zusammen in den case-Anweisungen verwenden, um die Lesbarkeit noch zu verbessern.

7
shogged

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

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

wobei die erste Zeile die beiden ersten guten Fälle und die zweite Zeile die letzte abdeckt.

Live Demo, wo ich rumgespielt habe und es deine Fälle übergibt.

7
gsamaras

Eine leichte Abweichung von @ GianPaolos feiner Antwort, die für manche einfacher zu lesen ist:

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))
{
  // ...
}
7
Matt

Ich würde auch Abkürzungsvariablen zur Verdeutlichung 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
}

Wenn Sie den Variablen MAJORTRUE und MAJORFALSE aussagekräftige Namen geben (und tatsächlich auch in bValue * vars), würde dies die Lesbarkeit und Wartung erheblich verbessern.

6
Gnudiff

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

Dies führt zwar zu mehr Codezeilen, und manche halten es entweder für übertrieben oder für unnötig. Ich würde vorschlagen, dass das Abstrahieren Ihrer Szenarien von den spezifischen Booleans die beste Möglichkeit ist, die Lesbarkeit zu erhalten.

Indem Sie die Dinge in Klassen unterteilen (verwenden Sie einfach Funktionen oder ein beliebiges anderes Werkzeug) mit verständlichen Namen - wir können die Bedeutungen hinter jedem Szenario viel einfacher darstellen. Noch wichtiger ist, dass in einem System mit vielen beweglichen Teilen - es einfacher ist, Ihre vorhandenen Systeme zu warten und zu verbinden (auch wenn dies mit zusätzlichem Code verbunden 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;
}
6
Bilkokuya

Es hängt davon ab, was sie repräsentieren.

Wenn zum Beispiel 1 ein Schlüssel ist und 2 und 3 zwei Personen sind, die zustimmen müssen (außer wenn sie sich auf NOT einigen, brauchen sie eine dritte Person - 4 - Bestätigen) die lesbarsten sind:

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

auf vielfachen Wunsch:

Key &&
    (
        (Alice && Bob)   
        || 
        ((!Alice && !Bob) && !Charlie)
    )
5
ispiro

Die bitweise Bedienung wirkt sehr sauber und verständlich. 

int bitwise = (bValue4 << 3) | (bValue3 << 2) | (bValue2 << 1) | (bValue1);
if (bitwise == 0b1111 || bitwise == 0b0111 || bitwise == 0b0001)
{
    //satisfying condition
}
4
Simonare

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 beliebige Gleichungen, die zu Ihnen passen.

3
yumoji

Nur eine persönliche Präferenz über die akzeptierte 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);
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

3
Owen Meyer

Vorausgesetzt, Sie können nur die Szenarioprüfung modifizieren, würde ich mich auf die Lesbarkeit konzentrieren und die Prüfung einfach in eine Funktion einbinden, so dass Sie einfach if(ScenarioA()) aufrufen können.


Vorausgesetzt, Sie möchten dies tatsächlich optimieren/wollen, würde ich empfehlen, die eng verknüpften Booleans in konstante Ganzzahlen zu konvertieren 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 Bestandteile, und Sie können mit einer switch-Anweisung in die richtige Situation springen und andere Entwickler 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.)

2
Tezra

Verschachtelte ifs könnten für manche Benutzer leichter lesbar 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;
}
2
sardok

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 undaus diesen Zuständendie booleschen Eigenschaften 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 Antwort von Gian Paolo , aber abhängig von Ihrer Situation kann dies viel besser gewartet werden:

  • Es gibt eine zentrale Reihe von Funktionen, die geändert werden können, 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 zu nicht behandelten enum Fällen in switch Anweisungen 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 überprüfen. 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 zu einem anderen übergeht.

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

1
jamesdlin

Auf diese Frage wurden mehrere richtige Antworten gegeben, aber ich würde eine andere Ansicht vertreten: wenn der Code zu kompliziert wirkt, ist etwas nicht ganz richtig. Es ist schwierig, den Code zu debuggen und eher "One-Use-Only" zu sein.

Im wirklichen Leben, wenn wir 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 derart präzises Muster verbunden sind, gilt es handelt sich um die Konfiguration einer "Entität" in unserem Modell

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

Offensichtlich ist der Weg zur Verringerung der Komplexität die Abstraktion und ein Werkzeug der Wahl in C++ ist das Object Paradigm.

Die Frage ist also: warum gibt es ein solches 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 welcher Stelle haben Sie Ihre anfängliche Konfiguration. als Array. Z.B. std::array hat einen Gleichheitsoperator:

An welcher Stelle 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, es kurz, klar und leicht nachprüfbar. In diesem Fall haben wir die Details der booleschen Ausdrücke an den Compiler delegiert.

1
fralau

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

Wenn jedoch die Logik für jeden Fall komplizierter wäre oder es viel mehr Fälle gibt, ist eine weitaus bessere Option die Verwendung des Chain-of-Responsibility Design-Musters.

Sie erstellen eine BaseValidator, die einen Verweis auf eine BaseValidator und eine Methode auf validate sowie 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;
    }
}

Dann erstellen Sie eine Reihe von Unterklassen, die von BaseValidator erben, und überschreibt die validate-Methode 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 zu verwenden, instanziieren Sie jeden Ihrer Prüfer und legen Sie fest, dass er die Wurzel der anderen sein soll:

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 seine eigene Klasse, die dafür zuständig ist, (a) zu bestimmen, ob die Validierung mit dem Fall jenseits übereinstimmt, und (b) die Validierung an eine andere Person in der Kette zu senden, falls 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, aber wenn dies nicht funktioniert, behandeln Sie sie eher wie Pseudocode. Ich habe auch unten ein vollständiges Python-Beispiel, das als Basis verwendet werden kann, wenn dies bevorzugt wird.

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))

Vielleicht finden Sie diesen Overkill für Ihr spezielles Beispiel, aber es erzeugt viel saubereren Code, wenn Sie mit einem viel komplizierteren Satz von Fällen zu tun haben, die erfüllt werden müssen.

0
Jim Cullen

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

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 das ist es ... Damit können Sie in der Zukunft problemlos weitere Bedingungen hinzufügen.

0
SCdev