Mam cztery bool
wartości:
bool bValue1;
bool bValue2;
bool bValue3;
bool bValue4;
Dopuszczalne wartości to:
Scenario 1 | Scenario 2 | Scenario 3
bValue1: true | true | true
bValue2: true | true | false
bValue3: true | true | false
bValue4: true | false | false
Na przykład ten scenariusz jest nie do przyjęcia:
bValue1: false
bValue2: true
bValue3: true
bValue4: true
W tej chwili wymyśliłem to if
stwierdzenie, aby wykryć złe scenariusze:
if(((bValue4 && (!bValue3 || !bValue2 || !bValue1)) ||
((bValue3 && (!bValue2 || !bValue1)) ||
(bValue2 && !bValue1) ||
(!bValue1 && !bValue2 && !bValue3 && !bValue4))
{
// There is some error
}
Czy można ulepszyć / uprościć tę logikę wypowiedzi?
c++
if-statement
Andrew Truckle
źródło
źródło
if
instrukcji. Dodatkowo, ponieważ są to flagi logiczne, możesz modelować każdy scenariusz jako stałą i porównywać z nią.if (!((bValue1 && bValue2 && bValue3) || (bValue1 && !bValue2 && !bValue3 && !bValue4)))
bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
Odpowiedzi:
Chciałbym dążyć do czytelności: masz tylko 3 scenariusze, radzisz sobie z nimi 3 oddzielnymi jeśli:
Łatwy do odczytania i debugowania, IMHO. Możesz również przypisać zmienną
whichScenario
, kontynuującif
.Mając tylko 3 scenariusze, nie wybrałbym czegoś takiego „jeśli pierwsze 3 wartości są prawdziwe, mogę uniknąć sprawdzania czwartej wartości”: utrudni to odczytanie i utrzymanie kodu.
Nie jest to eleganckie rozwiązanie
możena pewno, ale w tym przypadku jest ok: łatwe i czytelne.Jeśli twoja logika stanie się bardziej skomplikowana, wyrzuć ten kod i rozważ użycie czegoś więcej do przechowywania różnych dostępnych scenariuszy (jak sugeruje Zladeck).
Bardzo podoba mi się pierwsza sugestia podana w tej odpowiedzi : łatwa do odczytania, nie podatna na błędy, łatwa do utrzymania
(Prawie) poza tematem:
Nie piszę wielu odpowiedzi tutaj w StackOverflow. To naprawdę zabawne, że powyższa zaakceptowana odpowiedź jest zdecydowanie najbardziej cenioną odpowiedzią w mojej historii (nigdy wcześniej nie otrzymałem więcej niż 5-10 głosów za), podczas gdy w rzeczywistości nie jest to, co zwykle uważam za „właściwy” sposób na zrobienie tego.
Ale prostota jest często „właściwą drogą”, wydaje się, że wiele osób tak myśli i powinienem myśleć o tym bardziej niż ja :)
źródło
valid
i oddzielając je||
, zamiast mutowaniavalid
w oddzielnych blokach instrukcji. Nie mogę umieścić przykładu w komentarzu, ale możesz wyrównać w pionie||
operatory po lewej stronie, aby było to bardzo jasne; poszczególne warunki są już ujęte w nawiasy tak bardzo, jak trzeba (dlaif
), więc nie musisz dodawać żadnych znaków do wyrażeń poza tym, co już istnieje.if($bValue1)
który zawsze musi być prawdą, technicznie pozwalając na niewielką poprawę wydajności (chociaż mówimy tutaj o pomijalnych kwotach).bValue4
Chciałbym dążyć do prostoty i czytelności.
Pamiętaj, aby zastąpić nazwy scenariuszy, a także nazwy flag czymś opisowym. Jeśli ma to sens w przypadku Twojego konkretnego problemu, możesz rozważyć tę alternatywę:
Ważna jest tutaj logika predykatów. Opisuje Twoją domenę i jasno wyraża Twoje zamiary. Kluczem jest tutaj nadanie dobrych nazw wszystkim wejściom i zmiennym pośredniczącym. Jeśli nie możesz znaleźć dobrych nazw zmiennych, może to oznaczać, że opisujesz problem w niewłaściwy sposób.
źródło
Możemy użyć mapy Karnaugh i zredukować twoje scenariusze do logicznego równania. Użyłem solwera map online Karnaugh z obwodem dla 4 zmiennych.
To daje:
Zmiana
A, B, C, D
nabValue1, bValue2, bValue3, bValue4
to nic innego jak:Twoje
if
oświadczenie staje się więc:true
.true
scenariuszy do równania logicznego,true
dobrą praktyką jest dodanie odpowiednich komentarzy wskazujących scenariusze.źródło
//!(ABC + AB'C'D') (By K-Map logic)
. Byłby to dobry moment dla programisty, aby nauczyć się map K, jeśli jeszcze ich nie zna.E
iF
warunki i 4 nowe scenariusze? Ile czasu zajmujeif
poprawna aktualizacja tego oświadczenia? W jaki sposób przegląd kodu sprawdza, czy wszystko jest w porządku, czy nie? Problem nie dotyczy strony technicznej, ale strony „biznesowej”.A
:ABC + AB'C'D' = A(BC + B'C'D')
(można to nawet uwzględnić,A(B ^ C)'(C + D')
chociaż uważałbym, nazywając to „uproszczeniem”).Prawdziwe pytanie brzmi: co się stanie, gdy inny programista (lub nawet autor) musi zmienić ten kod kilka miesięcy później.
Sugerowałbym modelowanie tego jako flagi bitowe:
Jeśli istnieje wiele więcej scenariuszy lub więcej flag, podejście oparte na tabelach jest bardziej czytelne i rozszerzalne niż używanie flag. Obsługa nowego scenariusza wymaga tylko innego wiersza w tabeli.
źródło
SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;
2: unikaj zmiennych SCENARIO_X, a następnie przechowuj wszystkie dostępne scenariusze w pliku<std::set<int>
. Dodanie scenariusza będzie czymś, comySet.insert( true << 3 | false << 2 | true << 1 | false;
może być trochę przesadą dla zaledwie 3 scenariuszy, OP zaakceptował szybkie, brudne i łatwe rozwiązanie, które zasugerowałem w mojej odpowiedzi.std::find
?).scenario
wartości wydają mi się niepotrzebnie podatne na błędy.Moja poprzednia odpowiedź jest już zaakceptowaną odpowiedzią, dodaję tutaj coś, co uważam za czytelne, łatwe iw tym przypadku otwarte na przyszłe modyfikacje:
Zaczynając od odpowiedzi @ZdeslavVojkovic (którą uważam za całkiem dobrą), wymyśliłem to:
Zobacz, jak działa tutaj
Cóż, to jest „eleganckie i łatwe w utrzymaniu” rozwiązanie (IMHO), do którego zwykle dążę, ale tak naprawdę, w przypadku OP, moja poprzednia odpowiedź „garść jeśli” lepiej pasuje do wymagań OP, nawet jeśli nie jest elegancka ani łatwa do utrzymania.
źródło
Chciałbym również przedstawić inne podejście.
Mój pomysł polega na przekonwertowaniu wartości logicznych na liczbę całkowitą, a następnie porównaniu za pomocą szablonów wariadycznych:
Zwróć uwagę, jak ten system może obsługiwać do 32 booli jako wejście. zastępując
unsigned
zunsigned long long
(auint64_t
) zwiększa wsparcie 64 przypadkach. Jeśli nie podoba ci sięif (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)
, możesz również użyć innej metody szablonu wariadycznego:źródło
bitmap_from_bools
, lubbools_to_bitmap
?bools_to_unsigned
. Bitmapa to dobre słowo kluczowe; edytowane.summary!= 0b1111u &&...
.a != b || a != c
jest zawsze prawdą, jeślib != c
Oto uproszczona wersja:
Zauważ, oczywiście, że to rozwiązanie jest bardziej zaciemnione niż oryginalne, jego znaczenie może być trudniejsze do zrozumienia.
Aktualizacja: MSalters w komentarzach znalazło jeszcze prostsze wyrażenie:
źródło
simple
to rzeczywiście niejasne określenie. Wiele osób rozumie to w tym kontekście jako łatwiejsze do zrozumienia dla programisty, a nie dla kompilatora do generowania kodu, więc bardziej szczegółowe może być rzeczywiście prostsze.Rozważ przetłumaczenie swoich tabel tak bezpośrednio, jak to możliwe, do programu. Steruj programem opartym na stole, zamiast naśladować go za pomocą logiki.
teraz
to możliwie bezpośrednio koduje twoją tablicę prawdy do kompilatora.
Przykład na żywo .
Możesz również użyć
std::any_of
bezpośrednio:kompilator może wbudować kod i wyeliminować każdą iterację i zbudować dla Ciebie własną logikę. W międzyczasie twój kod odzwierciedla dokładnie to, jak pojmowałeś problem.
źródło
Podaję tutaj tylko swoją odpowiedź, tak jak w komentarzach, które ktoś zasugerował, aby pokazać moje rozwiązanie. Chcę podziękować wszystkim za ich spostrzeżenia.
Ostatecznie zdecydowałem się dodać trzy nowe
boolean
metody „scenariuszy” :Następnie mogłem zastosować te moje procedury walidacji w następujący sposób:
W mojej aplikacji na żywo 4 wartości bool są faktycznie wyodrębniane z a,
DWORD
który ma zakodowane 4 wartości.Jeszcze raz dziękuję wszystkim.
źródło
INCLUDE_ITEM1
itp. W lepszy sposób i wszyscy jesteście dobrzy. :)Nie widzę żadnych odpowiedzi, które mówią, aby nazwać scenariusze, chociaż rozwiązanie OP robi dokładnie to.
Dla mnie najlepiej jest umieścić komentarz dotyczący każdego scenariusza w nazwie zmiennej lub nazwie funkcji. Bardziej prawdopodobne jest, że zignorujesz komentarz niż imię, a jeśli twoja logika zmieni się w przyszłości, bardziej prawdopodobne jest, że zmienisz imię niż komentarz. Nie możesz refaktoryzować komentarza.
Jeśli planujesz ponowne użycie tych scenariuszy poza swoją funkcją (lub możesz chcieć), utwórz funkcję, która mówi, co ocenia (
constexpr
/noexcept
opcjonalne, ale zalecane):Jeśli to możliwe, utwórz te metody klasowe (jak w rozwiązaniu OP). Możesz użyć zmiennych wewnątrz swojej funkcji, jeśli myślisz, że nie będziesz ponownie używać logiki:
Kompilator najprawdopodobniej ustali, że jeśli bValue1 jest fałszywe, to wszystkie scenariusze są fałszywe. Nie martw się, że zrobisz to szybko, po prostu popraw i czytelnie. Jeśli profilujesz swój kod i stwierdzisz, że jest to wąskie gardło, ponieważ kompilator wygenerował nieoptymalny kod na -O2 lub wyższym, spróbuj go przepisać.
źródło
Sposób AC / C ++
To podejście jest skalowalne, tak jakby liczba prawidłowych warunków wzrosła, po prostu dodasz ich więcej do listy scenariuszy.
źródło
true
. Kompilator, który używa „cokolwiek niezerowego jest prawdą” powoduje niepowodzenie tego kodu. Pamiętaj, żetrue
musi zostać przekonwertowany na1
, po prostu nie musi być przechowywany jako taki.2 is not equal to true but evaluates to true
, mój kod nie wymuszaint 1 = true
i działa tak długo, jak wszystkie wartości true są konwertowane na tę samą wartość int, więc oto moje pytanie: dlaczego kompilator powinien działać losowo podczas konwersji zgodne z int, czy możesz rozwinąć więcej?memcmp
aby przetestować warunki logiczne, nie jest metodą C ++ i raczej wątpię, czy jest to ustalona metoda C.Łatwo zauważyć, że pierwsze dwa scenariusze są podobne - większość warunków jest w nich takich samych. Jeśli chcesz wybrać scenariusz, w którym się obecnie znajdujesz, możesz napisać to w ten sposób (jest to zmodyfikowane rozwiązanie @ gian-paolo ):
Idąc dalej, możesz zauważyć, że pierwsza wartość logiczna musi być zawsze prawdziwa, co jest warunkiem wejścia, więc możesz otrzymać:
Co więcej, teraz możesz wyraźnie zobaczyć, że bValue2 i bValue3 są w pewnym stopniu połączone - możesz wyodrębnić ich stan do niektórych funkcji zewnętrznych lub zmiennych o bardziej odpowiedniej nazwie (chociaż nie zawsze jest to łatwe lub właściwe):
Zrobienie tego w ten sposób ma pewne zalety i wady:
Jeśli przewidujesz, że nastąpią zmiany w powyższej logice, powinieneś użyć prostszego podejścia, które zostało przedstawione przez @ gian-paolo .
W przeciwnym razie, jeśli te warunki są dobrze ugruntowane i są rodzajem „solidnych reguł”, które nigdy się nie zmienią, rozważ mój ostatni fragment kodu.
źródło
Zgodnie z sugestią mch możesz zrobić:
gdzie pierwsza linia obejmuje dwa pierwsze dobre przypadki, a druga linia obejmuje ostatni.
Live Demo, gdzie się bawiłem i mija twoje sprawy.
źródło
Niewielka wariacja na temat dobrej odpowiedzi @ GianPaolo, która dla niektórych może być łatwiejsza do odczytania:
źródło
Każda odpowiedź jest zbyt złożona i trudna do odczytania. Najlepszym rozwiązaniem jest
switch()
stwierdzenie. Jest czytelny i ułatwia dodawanie / modyfikowanie dodatkowych przypadków. Kompilatory są również dobre w optymalizacjiswitch()
instrukcji.Możesz oczywiście używać stałych i LUB je razem w
case
instrukcjach dla jeszcze większej czytelności.źródło
Dla przejrzystości użyłbym również zmiennych skrótów. Jak wspomniano wcześniej, scenariusz 1 jest równy scenariuszowi 2, ponieważ wartość bValue4 nie wpływa na prawdziwość tych dwóch scenariuszy.
wtedy twoja ekspresja wygląda:
Nadanie znaczących nazw zmiennym MAJORTRUE i MAJORFALSE (a właściwie bValue * vars) bardzo pomogłoby w czytelności i utrzymaniu.
źródło
Skoncentruj się na czytelności problemu, a nie na konkretnym stwierdzeniu „jeśli”.
Chociaż spowoduje to powstanie większej liczby linii kodu, a niektórzy mogą uznać, że jest to przesada lub niepotrzebne. Sugerowałbym, że abstrahowanie scenariuszy z określonych wartości logicznych jest najlepszym sposobem zachowania czytelności.
Dzieląc rzeczy na klasy (nie krępuj się po prostu używać funkcji lub dowolnego innego narzędzia, które wolisz) o zrozumiałych nazwach - znacznie łatwiej możemy pokazać znaczenie każdego scenariusza. Co ważniejsze, w systemie z wieloma ruchomymi częściami - łatwiej jest utrzymywać i łączyć się z istniejącymi systemami (ponownie, pomimo ilości dodatkowego kodu).
źródło
To zależy od tego, co reprezentują.
Na przykład, jeśli 1 to klucz, a 2 i 3 to dwie osoby, które muszą się zgodzić (chyba że zgadzają się,
NOT
że potrzebują trzeciej osoby - 4 - do potwierdzenia), najbardziej czytelne może być:na popularne żądanie:
źródło
bValue
.Wykonywanie operacji bitowych wygląda bardzo czysto i zrozumiale.
źródło
Oznaczam a, b, c, d dla jasności i A, B, C, D dla uzupełnień
Równanie
Użyj dowolnych równań, które Ci odpowiadają.
źródło
prosty
źródło
To tylko osobiste preferencje w stosunku do zaakceptowanej odpowiedzi, ale napisałbym:
źródło
Po pierwsze, zakładając, że możesz modyfikować tylko sprawdzanie scenariusza, skupiłbym się na czytelności i po prostu zawinąłbym sprawdzenie w funkcję, abyś mógł po prostu wywołać
if(ScenarioA())
.Teraz, zakładając, że faktycznie chcesz / musisz to zoptymalizować, zaleciłbym przekonwertowanie ściśle powiązanych wartości logicznych na stałe liczby całkowite i użycie na nich operatorów bitowych
To sprawia, że wyrażanie scenariuszy jest tak łatwe, jak wyszczególnienie, co jest ich częścią, pozwala na użycie instrukcji switch, aby przejść do właściwego stanu i zmylić innych programistów, którzy wcześniej tego nie widzieli. (C # RegexOptions używa tego wzorca do ustawiania flag, nie wiem, czy istnieje przykład biblioteki C ++)
źródło
if
Niektórym osobom zagnieżdżone mogą być łatwiejsze do odczytania. Oto moja wersjaźródło
bValue1
bloku, możesz traktować wszystko w nim jako nową, świeżą stronę w swoim procesie myślowym. Założę się, że sposób podejścia do problemu może być bardzo osobisty, a nawet kulturowy.Na to pytanie udzielono kilku poprawnych odpowiedzi, ale ja przyjmuję inny pogląd: jeśli kod wygląda na zbyt skomplikowany, coś jest nie tak . Kod będzie trudny do debugowania i najprawdopodobniej będzie przeznaczony tylko do jednorazowego użytku.
W prawdziwym życiu, gdy znajdziemy taką sytuację:
Gdy cztery stany są połączone tak precyzyjnym wzorcem, mamy do czynienia z konfiguracją jakiejś „istoty” w naszym modelu .
Skrajną metaforą jest to, jak opisalibyśmy „istotę ludzką” w modelu, gdybyśmy nie byli świadomi ich istnienia jako jednostkowych bytów z komponentami połączonymi w określone stopnie swobody: musielibyśmy opisać niezależne stany „torsów”, „ramiona”, „nogi” i „głowa”, co utrudniłoby zrozumienie opisywanego systemu. Natychmiastowym rezultatem byłyby nienaturalnie skomplikowane wyrażenia logiczne.
Oczywiście sposobem na zmniejszenie złożoności jest abstrakcja, a narzędziem z wyboru w języku c ++ jest paradygmat obiektów .
Pytanie brzmi: dlaczego istnieje taki wzór? Co to jest i co przedstawia?
Ponieważ nie znamy odpowiedzi, możemy skorzystać z matematycznej abstrakcji: tablica : mamy trzy scenariusze, z których każdy jest teraz tablicą.
W którym momencie masz swoją początkową konfigurację. jako tablica. Np.
std::array
Ma operator równości:W tym momencie twoja składnia staje się:
Podobnie jak odpowiedź Gian Paolo, jest krótka, jasna i łatwa do zweryfikowania / debugowania. W tym przypadku przekazaliśmy szczegóły wyrażeń boolowskich do kompilatora.
źródło
Nie będziesz musiał martwić się o nieprawidłowe kombinacje flag boolowskich, jeśli pozbędziesz się flag logicznych.
Wyraźnie masz trzy stany (scenariusze). Byłoby lepiej wymodelować to i wyprowadzić właściwości boolowskie z tych stanów, a nie odwrotnie.
Jest to zdecydowanie więcej kodu niż w odpowiedzi Gian Paolo , ale w zależności od sytuacji może to być znacznie łatwiejsze do utrzymania:
enum
przypadków wswitch
instrukcjach spowoduje przechwycenie elementów pobierających właściwości, które nie obsługują tego scenariusza.Takie podejście ma również tę zaletę, że jest bardzo wydajne.
źródło
Moje 2 centy: zadeklaruj zmienną sumę (liczbę całkowitą), aby
Sprawdź sumę z warunkami, które chcesz i to wszystko. W ten sposób możesz łatwo dodać więcej warunków w przyszłości, dzięki czemu będzie dość czytelny.
źródło
Zaakceptowana odpowiedź jest dobra, gdy masz tylko 3 przypadki, a logika każdego z nich jest prosta.
Ale jeśli logika dla każdego przypadku byłaby bardziej skomplikowana lub jest o wiele więcej przypadków, znacznie lepszą opcją jest użycie wzorca projektowego łańcucha odpowiedzialności .
Tworzysz,
BaseValidator
który zawiera odwołanie do aBaseValidator
i metodę dovalidate
oraz metodę do wywołania walidacji w walidatorze, do którego się odwołuje.Następnie tworzysz kilka podklas, które dziedziczą
BaseValidator
povalidate
metodzie , nadpisując metodę logiką niezbędną dla każdego walidatora.Następnie użycie go jest proste, utwórz instancję każdego ze swoich walidatorów i ustaw każdy z nich jako katalog główny pozostałych:
Zasadniczo każdy przypadek walidacji ma swoją własną klasę, która jest odpowiedzialna za (a) określenie, czy walidacja pasuje do tego przypadku i (b) wysłanie walidacji do kogoś innego w łańcuchu, jeśli tak nie jest.
Zwróć uwagę, że nie znam C ++. Próbowałem dopasować składnię z kilku przykładów, które znalazłem online, ale jeśli to nie zadziała, potraktuj to bardziej jak pseudokod. Poniżej mam również kompletny działający przykład Pythona, który można wykorzystać jako podstawę, jeśli jest to preferowane.
Ponownie, możesz znaleźć to przesadę w swoim konkretnym przykładzie, ale tworzy znacznie czystszy kod, jeśli skończysz z dużo bardziej skomplikowanym zestawem przypadków, które należy spełnić.
źródło
Prostym podejściem jest znalezienie odpowiedzi, którą uważasz za akceptowalną.
Tak = (boolean1 && boolean2 && boolean3 && boolean4) + + ...
Teraz, jeśli to możliwe, uprość równanie, używając algebry Boole'a.
jak w tym przypadku, dopuszczalne1 i 2 łączą się z
(boolean1 && boolean2 && boolean3)
.Stąd ostateczna odpowiedź brzmi:
źródło
użyj pola bitowego :
PS :
To wielka szkoda dla CPPers. Ale UB nie jest moim zmartwieniem, sprawdź to na http://coliru.stacked-crooked.com/a/2b556abfc28574a1 .
źródło
unsigned char*
, chociaż myślę, że użycie czegoś podobnego((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1
prawdopodobnie byłoby bardziej wydajne.