Jak poprawić logikę, aby sprawdzić, czy 4 wartości logiczne pasują do niektórych przypadków


118

Mam cztery boolwartoś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 ifstwierdzenie, 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?


8
Użyłbym tabeli zamiast złożonej ifinstrukcji. Dodatkowo, ponieważ są to flagi logiczne, możesz modelować każdy scenariusz jako stałą i porównywać z nią.
Zdeslav Vojkovic

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

14
jakie są właściwie scenariusze? Często sprawy stają się dużo prostsze, jeśli podasz tylko nazwy własne, np.bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
idclev 463035818

6
Używając znaczących nazw, możesz wyodrębnić każdy złożony warunek do metody i wywołać tę metodę w warunku if. Byłoby znacznie bardziej czytelne i łatwe w utrzymaniu. np. spójrz na przykład podany w linku. refactoring.guru/decompose-conditional
Hardik Modha

Odpowiedzi:


195

Chciałbym dążyć do czytelności: masz tylko 3 scenariusze, radzisz sobie z nimi 3 oddzielnymi jeśli:

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

Łatwy do odczytania i debugowania, IMHO. Możesz również przypisać zmienną whichScenario, kontynuując if.

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że na 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 :)


1
jasne @hessamhedieh, jest ok tylko dla niewielkiej liczby dostępnych scenariuszy. jak powiedziałem, jeśli sprawy się skomplikują, lepiej poszukaj czegoś innego
Gian Paolo

4
Można to jeszcze bardziej uprościć, umieszczając wszystkie warunki w inicjatorze validi oddzielając je ||, zamiast mutowania validw 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 (dla if), więc nie musisz dodawać żadnych znaków do wyrażeń poza tym, co już istnieje.
Leushenko

1
@Leushenko, myślę, że mieszanie nawiasów, && i || warunki są dość podatne na błędy (ktoś w innej odpowiedzi powiedział, że w kodzie w OP wystąpił błąd w nawiasach, być może został poprawiony). Oczywiście, właściwe ustawienie może pomóc. Ale jaka jest zaleta? bardziej czytelne? łatwiejsze w utrzymaniu? Nie sądzę. Tylko moja opinia, oczywiście. I bądź pewien, naprawdę nienawidzę mieć wielu ifów w kodzie.
Gian Paolo

3
Zawarłbym to w sposób, if($bValue1)który zawsze musi być prawdą, technicznie pozwalając na niewielką poprawę wydajności (chociaż mówimy tutaj o pomijalnych kwotach).
Martijn

2
FWIW: są tylko 2 scenariusze: pierwsze 2 to ten sam scenariusz i nie zależą odbValue4
Dancrumb,

123

Chciałbym dążyć do prostoty i czytelności.

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

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

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ę:

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

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

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.


3
+1 To też bym zrobił. Tak jak wskazuje @RedFilter iw przeciwieństwie do akceptowanej odpowiedzi, jest to samodokumentacja. Nadanie scenariuszom własnych nazw w osobnym kroku jest znacznie bardziej czytelne.
Andreas,

106

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.

wprowadź opis obrazu tutaj

To daje:

wprowadź opis obrazu tutaj

Zmiana A, B, C, Dna bValue1, bValue2, bValue3, bValue4to nic innego jak:

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

Twoje ifoświadczenie staje się więc:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Mapy Karnaugha są szczególnie przydatne, gdy masz wiele zmiennych i wiele warunków, które powinny zostać ocenione true.
  • Po zredukowaniu truescenariuszy do równania logicznego, truedobrą praktyką jest dodanie odpowiednich komentarzy wskazujących scenariusze.

96
Chociaż technicznie poprawny, ten kod wymaga wielu komentarzy, aby mógł być edytowany przez innego programistę kilka miesięcy później.
Zdeslav Vojkovic

22
@ZdeslavVojkovic: Dodałbym tylko komentarz do równania. //!(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.
PW

11
Zgadzam się z tym, ale problem IMO polega na tym, że nie mapuje ona wyraźnie domeny problemu, tj. Sposobu, w jaki każdy warunek jest mapowany do określonego scenariusza, co utrudnia zmianę / rozszerzenie. Co się dzieje, gdy istnieją Ei Fwarunki i 4 nowe scenariusze? Ile czasu zajmuje ifpoprawna 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”.
Zdeslav Vojkovic

7
Myślę, że możesz wziąć pod uwagę 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”).
Maciej Piechotka

28
@PW Ten komentarz wydaje się równie zrozumiały jak kod i dlatego jest trochę bezcelowy. Lepszy komentarz wyjaśniłby, jak właściwie wymyśliłeś to równanie, tj. Że stwierdzenie powinno wywołać TTTT, TTTF i TFFF. W tym momencie równie dobrze możesz po prostu napisać te trzy warunki w kodzie i nie potrzebować żadnego wyjaśnienia.
Bernhard Barker

58

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:

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

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.

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

4
Nie jest to najbardziej konserwowalny, ale zdecydowanie upraszcza warunek if. Dlatego pozostawienie kilku komentarzy wokół operacji bitowych będzie tutaj absolutną koniecznością imo.
Adam Zahran,

6
IMO, tabela jest najlepszym podejściem, ponieważ lepiej skaluje się z dodatkowymi scenariuszami i flagami.
Zdeslav Vojkovic

Podoba mi się Twoje pierwsze rozwiązanie, łatwe do odczytania i otwarte na modyfikacje. Wprowadziłbym 2 ulepszenia: 1: przypisz wartości do scenariuszaX z wyraźnym wskazaniem użytych wartości boolowskich, np. 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ś, co mySet.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.
Gian Paolo

4
Jeśli używasz C ++ 14 lub nowszego, sugerowałbym zamiast tego użycie literałów binarnych w pierwszym rozwiązaniu - 0b1111, 0b1110 i 0b1000 jest znacznie bardziej przejrzyste. Prawdopodobnie możesz też to nieco uprościć używając standardowej biblioteki ( std::find?).
Bernhard Barker

2
Uważam, że tutaj literały binarne byłyby minimalnym wymaganiem, aby pierwszy kod był czysty. W obecnej formie jest całkowicie tajemniczy. Identyfikatory opisowe mogą pomóc, ale nie jestem nawet tego pewien. W rzeczywistości operacje bitowe mające na celu wygenerowanie scenariowartości wydają mi się niepotrzebnie podatne na błędy.
Konrad Rudolph

27

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:

#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;
}

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.


Wiesz, że zawsze możesz edytować swoją poprzednią odpowiedź i wprowadzać poprawki.
Andreas

20

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:

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

Zwróć uwagę, jak ten system może obsługiwać do 32 booli jako wejście. zastępując unsignedz unsigned long long(a uint64_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:

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

2
Uwielbiam to podejście, z wyjątkiem nazwy głównej funkcji: „od bool… do czego ?” - Dlaczego nie wprost bitmap_from_bools, lub bools_to_bitmap?
Konrad Rudolph

tak @KonradRudolph, nie mogłem wymyślić lepszej nazwy, z wyjątkiem może bools_to_unsigned. Bitmapa to dobre słowo kluczowe; edytowane.
Stack Danny

Myślę, że chcesz summary!= 0b1111u &&.... a != b || a != cjest zawsze prawdą, jeślib != c
MooseBoys

17

Oto uproszczona wersja:

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

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:

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

1
Tak, ale trudno to zrozumieć. Ale dzięki za sugestię.
Andrew Truckle,

Porównałem zdolność kompilatorów do upraszczania wyrażeń z twoim uproszczeniem jako odniesieniem: eksplorator kompilatorów . gcc nie znalazł optymalnej wersji, ale jego rozwiązanie jest nadal dobre. Wydaje się, że Clang i MSVC nie wykonują żadnego uproszczenia wyrażeń logicznych.
Oliv

1
@AndrewTruckle: zwróć uwagę, że jeśli potrzebujesz bardziej czytelnej wersji, powiedz to. Powiedziałeś „uproszczona”, ale akceptujesz jeszcze bardziej szczegółową wersję niż oryginalna.
geza

1
simpleto 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.
Zdeslav Vojkovic

1
@IsmaelMiguel: gdy formuła logiczna jest zoptymalizowana pod kątem liczby terminów, zwykle traci się pierwotne znaczenie. Ale można to skomentować, aby było jasne, co robi. Nawet w przypadku zaakceptowanej odpowiedzi komentarz nie zaszkodziłby.
geza

12

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.

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

teraz

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

to możliwie bezpośrednio koduje twoją tablicę prawdy do kompilatora.

Przykład na żywo .

Możesz również użyć std::any_ofbezpośrednio:

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

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.


Pierwsza wersja jest tak łatwa do odczytania i tak łatwa do utrzymania, że ​​bardzo mi się podoba. Drugi jest trudniejszy do odczytania, przynajmniej dla mnie, i wymaga poziomu umiejętności c ++, być może powyżej średniej, na pewno powyżej mojego. Nie każdy jest w stanie napisać. Właśnie dowiedziałem się czegoś nowego, dzięki
Gian Paolo,

11

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 booleanmetody „scenariuszy” :

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

Następnie mogłem zastosować te moje procedury walidacji w następujący sposób:

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

W mojej aplikacji na żywo 4 wartości bool są faktycznie wyodrębniane z a, DWORDktóry ma zakodowane 4 wartości.

Jeszcze raz dziękuję wszystkim.


1
Dzięki za udostępnienie rozwiązania. :) To właściwie lepsze niż kompleks, jeśli warunkuje piekło. Może nadal możesz nazywać INCLUDE_ITEM1itp. W lepszy sposób i wszyscy jesteście dobrzy. :)
Hardik Modha

1
@HardikModha Cóż, technicznie rzecz biorąc, są to „przedmioty studenckie”, a flaga wskazuje, czy mają być „uwzględnione”. Myślę więc, że nazwa, choć brzmi ogólnikowo, ma w tym kontekście znaczenie. :)
Andrew Truckle

11

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/ noexceptopcjonalne, ale zalecane):

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

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:

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;

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


Podobało mi się to nieco bardziej niż rozwiązanie Giana Paolo (już fajne): unika przepływu sterowania i używania zmiennej, która jest nadpisywana - bardziej funkcjonalny styl.
Dirk Herrmann

9

Sposób AC / C ++

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

To podejście jest skalowalne, tak jakby liczba prawidłowych warunków wzrosła, po prostu dodasz ich więcej do listy scenariuszy.


Jestem jednak pewien, że to źle. Zakłada, że ​​kompilator używa tylko jednej reprezentacji binarnej dla true. Kompilator, który używa „cokolwiek niezerowego jest prawdą” powoduje niepowodzenie tego kodu. Pamiętaj, że truemusi zostać przekonwertowany na 1, po prostu nie musi być przechowywany jako taki.
MSalters

@MSalters, tnx, rozumiem twój punkt widzenia i jestem tego świadomy 2 is not equal to true but evaluates to true, mój kod nie wymusza int 1 = truei 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?
hessam hedieh

Wykonywanie a, memcmpaby przetestować warunki logiczne, nie jest metodą C ++ i raczej wątpię, czy jest to ustalona metoda C.
Konrad Rudolph

@hessamhedieh: Problem z twoją logiką polega na „konwersji wartości true na bazową int”. To jest nie jak działają kompilatory,
MSalters

Twój kod zwiększa złożoność od O (1) do O (n). Nie sposób przejść w żadnym języku - odłóż na bok C / C ++.
mabel

9

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

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

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ć:

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
}

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

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
}

Zrobienie tego w ten sposób ma pewne zalety i wady:

  • warunki są mniejsze, więc łatwiej o nich myśleć,
  • łatwiej jest zrobić przyjemną zmianę nazwy, aby te warunki były bardziej zrozumiałe,
  • ale wymagają zrozumienia zakresu,
  • ponadto jest bardziej sztywny

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.


7

Zgodnie z sugestią mch możesz zrobić:

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

gdzie pierwsza linia obejmuje dwa pierwsze dobre przypadki, a druga linia obejmuje ostatni.

Live Demo, gdzie się bawiłem i mija twoje sprawy.


7

Niewielka wariacja na temat dobrej odpowiedzi @ GianPaolo, która dla niektórych może być łatwiejsza do odczytania:

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

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 optymalizacji switch()instrukcji.

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

Możesz oczywiście używać stałych i LUB je razem w caseinstrukcjach dla jeszcze większej czytelności.


Będąc starym programistą C, zdefiniowałbym makro "PackBools" i użyłbym go zarówno dla "przełącznika (PackBools (a, b, c, d))", jak i dla przypadków, np. Bezpośrednio "case PackBools (prawda , true ...) "lub zdefiniuj je jako lokalne stałe.eg" const unsigned int scenario1 = PackBools (true, true ...); "
Simon F

6

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.

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

wtedy twoja ekspresja wygląda:

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

Nadanie znaczących nazw zmiennym MAJORTRUE i MAJORFALSE (a właściwie bValue * vars) bardzo pomogłoby w czytelności i utrzymaniu.


6

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

#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;
}

5
W pewnym momencie gadatliwość zaczyna szkodzić czytelności. Myślę, że to posuwa się za daleko.
JollyJoker,

2
@JollyJoker Właściwie zgadzam się w tej konkretnej sytuacji - jednak moje przeczucie ze sposobu, w jaki OP nazwał wszystko niezwykle ogólnie, jest takie, że ich „prawdziwy” kod jest prawdopodobnie o wiele bardziej złożony niż przykład, który podali. Naprawdę, po prostu chciałem umieścić tę alternatywę, ponieważ w ten sposób ustrukturyzowałbym ją na coś znacznie bardziej złożonego / zaangażowanego. Ale masz rację - w przypadku konkretnego przykładu PO jest zbyt rozwlekły i pogarsza sprawę.

5

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ć:

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

na popularne żądanie:

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

2
Możesz mieć rację, ale używanie liczb do zilustrowania twojego punktu widzenia umniejsza twoją odpowiedź. Spróbuj użyć nazw opisowych.
jxh

1
@jxh To są użyte liczby OP. Właśnie usunąłem bValue.
ispiro,

@jxh Mam nadzieję, że teraz jest lepiej.
ispiro,

4

Wykonywanie operacji bitowych wygląda bardzo czysto i zrozumiale.

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

1
Porównanie bitowe wydaje mi się czytelne. Kompozycja natomiast wygląda sztucznie.
xtofl

3

Oznaczam a, b, c, d dla jasności i A, B, C, D dla uzupełnień

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

Równanie

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

Użyj dowolnych równań, które Ci odpowiadają.


3
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1 zawsze musi być prawdziwe
  • b2 musi zawsze być równe b3
  • a b4 nie może być fałszem, jeśli b2 (i b3) są prawdziwe

prosty


3

To tylko osobiste preferencje w stosunku do zaakceptowanej odpowiedzi, ale napisałbym:

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

2

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

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
}

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


W rzeczywistości nie używam czterech wartości bool, ale DWORD z czterema osadzonymi BOOLami. Za późno, żeby to teraz zmienić. Ale dzięki za sugestię.
Andrew Truckle,

2

ifNiektórym osobom zagnieżdżone mogą być łatwiejsze do odczytania. Oto moja wersja

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

Osobiście zazwyczaj unikałbym zagnieżdżania, jeśli stwierdzenia, jeśli to możliwe. Chociaż ten przypadek jest ładny i czytelny, po dodaniu nowych możliwości zagnieżdżenie może stać się bardzo trudne do odczytania. Ale jeśli scenariusze nigdy się nie zmienią, to z pewnością jest to ładne i czytelne rozwiązanie.
Dnomyar96

@ Dnomyar96 Zgadzam się. Osobiście unikam też zagnieżdżonych ifs. Czasami, jeśli logika jest skomplikowana, łatwiej jest mi ją zrozumieć, rozkładając ją na części. Na przykład, kiedy wejdziesz do bValue1bloku, 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.
sardok

1

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ę:

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

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

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

W którym momencie masz swoją początkową konfigurację. jako tablica. Np. std::arrayMa operator równości:

W tym momencie twoja składnia staje się:

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

}

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.


1

Nie będziesz musiał martwić się o nieprawidłowe kombinacje flag boolowskich, jeśli pozbędziesz się flag logicznych.

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

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.

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

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:

  • Istnieje centralny zestaw funkcji do zmodyfikowania, jeśli zostaną dodane dodatkowe właściwości logiczne lub scenariusze.
    • Dodanie właściwości wymaga dodania tylko jednej funkcji.
    • W przypadku dodawania scenariusza włączenie ostrzeżeń kompilatora dotyczących nieobsłużonych enumprzypadków w switchinstrukcjach spowoduje przechwycenie elementów pobierających właściwości, które nie obsługują tego scenariusza.
  • Jeśli chcesz dynamicznie modyfikować właściwości logiczne, nie musisz wszędzie ponownie sprawdzać ich kombinacji. Zamiast przełączać poszczególne flagi logiczne (co może skutkować nieprawidłowymi kombinacjami flag), zamiast tego miałbyś maszynę stanu, która przechodzi z jednego scenariusza do drugiego.

Takie podejście ma również tę zaletę, że jest bardzo wydajne.


0

Moje 2 centy: zadeklaruj zmienną sumę (liczbę całkowitą), aby

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

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.


0

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, BaseValidatorktóry zawiera odwołanie do a BaseValidatori metodę do validateoraz metodę do wywołania walidacji w walidatorze, do którego się odwołuje.

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

Następnie tworzysz kilka podklas, które dziedziczą BaseValidatorpo validatemetodzie , nadpisując metodę logiką niezbędną dla każdego walidatora.

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

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:

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

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.

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

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


-2

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:

(boolean1 && boolean2 && boolean3) || 
((boolean1 && !boolean2 && !boolean3 && !boolean4)

-3

użyj pola bitowego :

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 :

To wielka szkoda dla CPPers. Ale UB nie jest moim zmartwieniem, sprawdź to na http://coliru.stacked-crooked.com/a/2b556abfc28574a1 .


2
Powoduje to UB z powodu dostępu do nieaktywnego pola unii.
HolyBlackCat

Formalnie jest to UB w C ++, nie możesz ustawić jednego członka unii i czytać z innego. Z technicznego punktu widzenia może być lepiej zaimplementować metody pobierające \ ustawiające na podstawie szablonu dla bitów o wartości całkowitej.
Swift - Friday Pie

Myślę, że zachowanie zmieniłoby się na zdefiniowane przez implementację, gdyby ktoś przekonwertował adres związku na adres unsigned char*, chociaż myślę, że użycie czegoś podobnego ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1prawdopodobnie byłoby bardziej wydajne.
supercat
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.