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
So ist dieses Szenario beispielsweise nicht akzeptabel:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
Im Moment habe ich mir diese if
Aussage 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?
c++
if-statement
Andrew Truckle
quelle
quelle
if
Anweisung verwenden. Da es sich um boolesche Flags handelt, können Sie außerdem jedes Szenario als Konstante modellieren und dagegen prüfen.if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
Antworten:
Ich würde die Lesbarkeit anstreben: Sie haben nur 3 Szenarien, behandeln Sie sie mit 3 separaten ifs:
Einfach zu lesen und zu debuggen, IMHO. Sie können auch eine Variable zuweisen,
whichScenario
während Sie mit dem fortfahrenif
.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
vielleichtsicherlich, 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 :)
quelle
valid
und in den Initialisierer gestapelt und getrennt werden||
, anstattvalid
in 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ürif
) sein müssen, sodass Sie den Ausdrücken keine Zeichen hinzufügen müssen, die über das hinausgehen, was bereits vorhanden ist.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).bValue4
Ich würde auf Einfachheit und Lesbarkeit abzielen.
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:
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.
quelle
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.
Dies ergibt:
Ändern
A, B, C, D
zubValue1, bValue2, bValue3, bValue4
, dies ist nichts anderes als:So wird Ihre
if
Aussage:true
.true
Szenarien auf eine logische Gleichung reduziert haben , empfiehlt es sich, relevante Kommentare hinzuzufügen, die dietrue
Szenarien angeben .quelle
//!(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.E
undF
Bedingungen und 4 neue Szenarien? Wie lange dauert es, dieseif
Anweisung 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.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).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:
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.
quelle
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 nurmySet.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.std::find
?) Ein wenig vereinfachen .scenario
mir die Bitoperationen zum Erzeugen des Werts als unnötig fehleranfällig.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:
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.
quelle
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:
Beachten Sie, wie dieses System bis zu 32 Bools als Eingabe unterstützen kann. Das Ersetzen
unsigned
durchunsigned long long
(oderuint64_t
) erhöht die Unterstützung auf 64 Fälle. Wenn Ihnen das nicht gefälltif (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)
, können Sie auch eine andere variable Vorlagenmethode verwenden:quelle
bitmap_from_bools
oderbools_to_bitmap
?bools_to_unsigned
. Bitmap ist ein gutes Schlüsselwort. bearbeitet.summary!= 0b1111u &&...
.a != b || a != c
ist immer wahr, wennb != c
Hier ist eine vereinfachte Version:
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:
quelle
simple
ist 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.Ü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.
jetzt
Dadurch wird Ihre Wahrheitstabelle direkt wie möglich in den Compiler codiert.
Live-Beispiel .
Sie können auch
std::any_of
direkt verwenden: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.
quelle
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 :Dann konnte ich meine Validierungsroutine folgendermaßen anwenden:
In meiner Live-Anwendung werden die 4 Bool-Werte tatsächlich aus einem extrahiert, in
DWORD
dem 4 Werte codiert sind.Nochmals vielen Dank an alle.
quelle
INCLUDE_ITEM1
usw. noch besser benennen und Sie sind alle gut. :)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
/noexcept
optional, aber empfohlen):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:
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.
quelle
AC / C ++ Weg
Dieser Ansatz ist skalierbar, als ob die Anzahl der gültigen Bedingungen zunimmt. Sie können einfach weitere davon zur Szenarioliste hinzufügen.
quelle
true
. Ein Compiler, der "alles, was nicht Null ist, ist wahr" verwendet, führt dazu, dass dieser Code fehlschlägt. Beachten Sie, dasstrue
muss konvertieren zu1
, es muss einfach nicht sein gespeichert als solche.2 is not equal to true but evaluates to true
mein Code nicht erzwingtint 1 = true
und 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?memcmp
zum Testen von booleschen Bedingungen ist nicht die C ++ - Methode, und ich bezweifle eher, dass es sich auch um eine etablierte C-Methode handelt.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):
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:
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):
Dies zu tun hat einige Vor- und Nachteile:
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.
quelle
Wie von mch vorgeschlagen, können Sie Folgendes tun:
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.
quelle
Eine kleine Variation von @ GianPaolos feiner Antwort, die einige vielleicht leichter zu lesen finden:
quelle
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 .Sie können natürlich Konstanten und ODER-Verknüpfungen in den
case
Anweisungen verwenden, um die Lesbarkeit zu verbessern.quelle
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.
dann wird dein Ausdruck:
Das Vergeben aussagekräftiger Namen für MAJORTRUE- und MAJORFALSE-Variablen (sowie für bValue * -Vars) würde die Lesbarkeit und Wartung erheblich verbessern.
quelle
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).
quelle
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
NOT
sie eine dritte Person benötigen - 4 -, um dies zu bestätigen), ist möglicherweise am besten lesbar:auf vielfachen Wunsch:
quelle
bValue
.Die bitweise Bedienung sieht sehr sauber und verständlich aus.
quelle
Ich bezeichne a, b, c, d aus Gründen der Klarheit und A, B, C, D für Ergänzungen
Gleichung
Verwenden Sie alle Gleichungen, die zu Ihnen passen.
quelle
einfach
quelle
Nur eine persönliche Präferenz gegenüber der akzeptierten Antwort, aber ich würde schreiben:
quelle
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
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.)
quelle
Verschachtelte
if
s könnten für manche Menschen leichter zu lesen sein. Hier ist meine Versionquelle
bValue1
Block 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.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:
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.
An diesem Punkt haben Sie Ihre Erstkonfiguration. als Array. Z.B
std::array
hat einen Gleichheitsoperator:An welchem Punkt wird Ihre Syntax:
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.
quelle
Sie müssen sich keine Gedanken über ungültige Kombinationen von Booleschen Flags machen, wenn Sie die Booleschen Flags entfernen.
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.
Dies ist definitiv mehr Code als in Gian Paolos Antwort , aber abhängig von Ihrer Situation könnte dies viel wartbarer sein:
enum
Fälle inswitch
Anweisungen aktivieren , werden Eigenschafts-Getter abgefangen , die dieses Szenario nicht behandeln.Dieser Ansatz hat auch den Nebeneffekt, sehr effizient zu sein.
quelle
Meine 2 Cent: deklarieren Sie eine variable Summe (Integer) damit
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.
quelle
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,
BaseValidator
die einen Verweis auf aBaseValidator
und eine Methode aufvalidate
und eine Methode zum Aufrufen der Validierung für den referenzierten Validator enthält.Anschließend erstellen Sie eine Reihe von Unterklassen, die von der erben
BaseValidator
, und überschreiben dievalidate
Methode mit der für jeden Validator erforderlichen Logik.Dann ist es einfach, jeden Ihrer Validatoren zu instanziieren und jeden von ihnen als Wurzel der anderen festzulegen:
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.
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.
quelle
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:
quelle
verwenden Bit - Feld :
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 .
quelle
unsigned char*
, obwohl ich denke, dass((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1
es wahrscheinlich effizienter wäre , einfach so etwas zu verwenden.