web-dev-qa-db-fra.com

Comment améliorer la logique pour vérifier si 4 valeurs booléennes correspondent à certains cas

J'ai quatre valeurs bool:

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

Les valeurs acceptables sont:

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

Ainsi, par exemple, ce scénario n'est pas acceptable:

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

Pour le moment, je propose cette instruction if afin de détecter les mauvais scénarios:

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

Cette logique d'énoncé peut-elle être améliorée/simplifiée?

115
Andrew Truckle

Je viserais la lisibilité: vous n’avez que 3 scénarios, traitez-les avec 3 si différents:

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

Facile à lire et à déboguer, à mon humble avis. En outre, vous pouvez affecter une variable whichScenario tout en procédant avec if.

Avec seulement 3 scénarios, je n'irais pas avec quelque chose de ce type "si les 3 premières valeurs sont vraies, je peux éviter de vérifier la valeur suivante": cela rendra votre code plus difficile à lire et à maintenir.

Pas une solution élégante peut être sûrement, mais dans ce cas c'est ok: facile et lisible.

Si votre logique devient plus compliquée, jetez ce code et envisagez d'utiliser quelque chose de plus pour stocker différents scénarios disponibles (comme le suggère Zladeck).

J'aime vraiment la première suggestion donnée dans cette réponse : facile à lire, pas sujette aux erreurs, maintenable

(Presque) hors sujet:

Je n'écris pas beaucoup de réponses ici chez StackOverflow. C'est vraiment drôle que la réponse acceptée ci-dessus soit de loin la réponse la plus appréciée de mon histoire (jamais plus de 5 à 10 votes positifs auparavant), alors qu'en réalité ce n'est pas ce que je pense habituellement est la "bonne" façon de le faire.

Mais la simplicité est souvent "la bonne façon de le faire", beaucoup de gens semblent penser cela et je devrais le penser plus que moi :)

196
Gian Paolo

Je viserais la simplicité et la lisibilité.

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

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

Assurez-vous de remplacer les noms des scénarios ainsi que les noms des indicateurs par quelque chose de descriptif. Si cela convient à votre problème spécifique, vous pouvez envisager cette alternative:

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

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

Ce qui est important ici n’est pas la logique des prédicats. Il décrit votre domaine et exprime clairement votre intention. La clé ici est de donner à toutes les entrées et à toutes les variables intermédiaires de bons noms. Si vous ne trouvez pas de bons noms de variables, cela peut indiquer que vous décrivez le problème de manière erronée.

121
Anders

Nous pouvons utiliser une carte Karnaugh et réduire vos scénarios à une équation logique .J'ai utilisé la carte Karnaugh en ligne solutionneur avec circuit pour 4 variables.

 enter image description here

Ceci donne: 

 enter image description here

Changer A, B, C, D en bValue1, bValue2, bValue3, bValue4, ce n’est rien mais: 

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

Donc, votre déclaration if devient:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Les cartes de Karnaugh sont particulièrement utiles lorsque vous avez plusieurs variables et plusieurs conditions qui devraient évaluer true
  • Après avoir réduit les scénarios true à une équation logique, il est recommandé d’ajouter des commentaires pertinents indiquant les scénarios true.
104
P.W

La vraie question est la suivante: que se passera-t-il lorsqu'un autre développeur (ou même auteur) devra modifier ce code quelques mois plus tard.

Je suggérerais de modéliser ceci comme des drapeaux de bits:

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

S'il y a beaucoup plus de scénarios ou plus d'indicateurs, une approche table est plus lisible et plus extensible que d'utiliser des indicateurs. La prise en charge d'un nouveau scénario nécessite simplement une autre ligne dans la table.

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
Zdeslav Vojkovic

Ma réponse précédente est déjà la réponse acceptée, j'ajoute ici quelque chose que je pense être à la fois lisible, facile et dans ce cas ouvert à de futures modifications:

En commençant par la réponse de @ZdeslavVojkovic (que je trouve assez bonne), j'ai proposé ceci:

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

Voir au travail ici

Eh bien, c’est la solution "élégante et maintenable" (IMHO) que je vise habituellement, mais en réalité, dans le cas des PO, ma réponse précédente "tas de si" correspond mieux aux exigences du PO, même si elle n’est ni élégante, ni maintenable.

28
Gian Paolo

Je voudrais aussi soumettre une autre approche.

Mon idée est de convertir les bools en un entier, puis de les comparer à l'aide de modèles variadiques:

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

Remarquez comment ce système peut prendre en charge jusqu'à 32 booléens en entrée. Le remplacement de unsigned par unsigned long long (ou uint64_t) augmente la prise en charge de 64 cas . Si vous n'aimez pas la if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u), vous pouvez également utiliser une autre méthode de modèle variadique:

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

Voici une version simplifiée:

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

Notez bien sûr que cette solution est plus obscurcie que la solution originale, son sens peut être plus difficile à comprendre.


Mise à jour: MSalters dans les commentaires a trouvé une expression encore plus simple:

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

Pensez à traduire vos tables aussi directement que possible dans votre programme. Pilotez le programme en dehors de la table, au lieu de l'imiter avec logique.

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

à présent

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

ceci directement que possible code votre table de vérité dans le compilateur.

Exemple live .

Vous pouvez également utiliser std::any_of directement:

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

le compilateur peut intégrer le code, éliminer toute itération et créer sa propre logique. Pendant ce temps, votre code reflète exactement comment vous avez conçu le problème.

12

Je ne fais que donner ma réponse ici, comme dans les commentaires, quelqu'un a suggéré de montrer ma solution. Je tiens à remercier tout le monde pour leurs idées.

Finalement, j'ai choisi d'ajouter trois nouvelles méthodes "scenario" boolean:

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

Ensuite, j'ai pu appliquer ceux-ci à ma routine de validation comme ceci:

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

Dans mon application live, les 4 valeurs bool sont en fait extraites d'une DWORD dans laquelle 4 valeurs sont encodées.

Merci encore à tous.

11
Andrew Truckle

Je ne vois pas de réponse disant de nommer les scénarios, bien que la solution du PO fasse exactement cela.

Pour moi, il est préférable d'encapsuler le commentaire de chaque scénario dans un nom de variable ou un nom de fonction. Vous êtes plus susceptible d'ignorer un commentaire qu'un nom et si votre logique change, vous aurez plus de chances de changer un nom qu'un commentaire. Vous ne pouvez pas refactoriser un commentaire.

Si vous envisagez de réutiliser ces scénarios en dehors de votre fonction (ou souhaitez le faire), créez une fonction qui dit ce qu'elle évalue ( constexpr / noexcept facultatif mais recommandé):

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

Faites ces méthodes de classe si possible (comme dans la solution de OP). Vous pouvez utiliser des variables à l'intérieur de votre fonction si vous ne pensez pas réutiliser la logique:

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;

Le compilateur déterminera probablement que si bValue1 est false, tous les scénarios sont faux. Ne vous inquiétez pas pour le rendre rapide, juste correct et lisible. Si vous profilez votre code et constatez qu'il s'agit d'un goulot d'étranglement, car le compilateur a généré un code sous-optimal à -O2 ou plus, essayez de le réécrire.

11
Erroneous

Une manière C/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;
}

Cette approche est évolutive, car si le nombre de conditions valides augmente, vous pouvez simplement en ajouter d'autres à la liste des scénarios. 

9
hessam hedieh

Il est facile de remarquer que les deux premiers scénarios sont similaires: ils partagent la plupart des conditions. Si vous voulez sélectionner votre scénario actuel, vous pouvez l'écrire comme ceci (c'est une solution modifiée de @ 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

En allant plus loin, vous remarquerez que le premier booléen doit être toujours vrai, ce qui est une condition d'entrée, de sorte que vous pouvez vous retrouver avec:

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
}

Encore plus, vous pouvez maintenant voir clairement que bValue2 et bValue3 sont un peu liées - vous pouvez extraire leur état à des fonctions externes ou à des variables avec un nom plus approprié (ce n’est pas toujours facile ni approprié):

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
}

Cette façon de procéder présente certains avantages et inconvénients:

  • les conditions sont plus petites, il est donc plus facile de raisonner à leur sujet,
  • il est plus facile de renommer Nice pour rendre ces conditions plus compréhensibles,
  • mais, ils ont besoin de comprendre la portée, 
  • de plus c'est plus rigide

Si vous prévoyez qu'il y aura des changements dans la logique ci-dessus, vous devez utiliser une approche plus simple telle que présentée par @ gian-paolo .

Sinon, si ces conditions sont bien établies et constituent en quelque sorte des "règles solides" qui ne changeront jamais, considérez mon dernier extrait de code.

9
Michał Łoś

Chaque réponse est trop complexe et difficile à lire. La meilleure solution est une instruction switch(). Il est à la fois lisible et facilite l’ajout/la modification de cas supplémentaires. Les compilateurs sont également efficaces pour optimiser les instructions switch().

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

Vous pouvez bien sûr utiliser les constantes et OR ensemble dans les instructions case pour une lisibilité encore meilleure.

7
shogged

Comme suggéré par mch, vous pourriez faire:

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

où la première ligne couvre les deux premiers bons cas, et la deuxième ligne couvre le dernier.

Live Demo, où j'ai joué et ça passe vos cas.

7
gsamaras

Une légère variation de la bonne réponse de @ GianPaolo, que certains trouveront peut-être plus facile à lire:

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

Je voudrais aussi utiliser des variables de raccourci pour plus de clarté. Comme indiqué précédemment, le scénario 1 équivaut au scénario 2, car la valeur de bValue4 n'influence pas la vérité de ces deux scénarios.

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

alors votre expression devient:

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

Donner des noms significatifs aux variables MAJORTRUE et MAJORFALSE (ainsi qu’à bValue * vars) aiderait beaucoup en termes de lisibilité et de maintenance.

6
Gnudiff

Concentrez-vous sur la lisibilité du problème, pas sur la déclaration spécifique "si".

Bien que cela produise plus de lignes de code, certains pourraient considérer que cela est excessif ou inutile. Je suggérerais qu'abstraire vos scénarios des booléens spécifiques est le meilleur moyen de maintenir la lisibilité.

En scindant les éléments en classes (noms libres, avec des noms compréhensibles, vous pouvez utiliser simplement des fonctions ou tout autre outil de votre choix) - nous pouvons beaucoup plus facilement montrer la signification de chaque scénario. Plus important encore, dans un système comportant de nombreuses pièces mobiles, il est plus facile de maintenir et d’intégrer vos systèmes existants (encore une fois, malgré l’importance du code supplémentaire nécessaire).

#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

Cela dépend de ce qu'ils représentent.

Par exemple, si 1 est une clé, et 2 et 3 sont deux personnes qui doivent accepter (sauf s’ils s’accordent sur NOT ils ont besoin d’une tierce personne - 4 - à confirmer) le plus lisible pourrait être:

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

à la demande populaire:

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

Faire des opérations au niveau des bits semble très propre et compréhensible. 

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

Je note a, b, c, d pour plus de clarté et a, b, c et d pour compléments

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

Équation

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

Utilisez les équations qui vous conviennent.

3
yumoji

Juste une préférence personnelle par rapport à la réponse acceptée, mais je voudrais écrire:

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
François Gueguen
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1 doit toujours être vrai 
  • b2 doit toujours être égal à b3 
  • et b4 ne peut pas être faux si b2 (et b3) sont vrais

simple

3
Owen Meyer

Premièrement, en supposant que vous ne puissiez modifier que le contrôle de scénario, je me concentrerais sur la lisibilité et encapsulerais simplement le contrôle dans une fonction afin que vous puissiez simplement appeler if(ScenarioA()).


Maintenant, en supposant que vous souhaitiez/ayez réellement besoin d'optimiser cela, je vous recommanderais de convertir les booléens étroitement liés en un nombre entier constant et d'utiliser des opérateurs de bits.

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
}

Cela permet d’exprimer les scénarios aussi simplement que d’énumérer ce qui en fait partie, vous permet d’utiliser une instruction switch pour atteindre le bon état et de semer la confusion parmi les autres développeurs qui n’ont jamais vu cela auparavant. (C # RegexOptions utilise ce modèle pour définir des indicateurs, je ne sais pas s’il existe un exemple de bibliothèque c ++)

2
Tezra

ifs imbriqué pourrait être plus facile à lire pour certaines personnes. Voici ma 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

Vous n'aurez pas à vous soucier des combinaisons non valides de drapeaux booléens si vous vous en débarrassez.

Les valeurs acceptables sont:

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

Vous avez clairement trois états (scénarios). Il serait préférable de modéliser cela et de dériver les propriétés booléennes à partir de ces états, et non l'inverse.

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

C'est certainement plus de code que dans La réponse de Gian Paolo , mais selon votre situation, cela pourrait être beaucoup plus facile à gérer:

  • Il existe un ensemble central de fonctions à modifier si des propriétés ou des scénarios booléens supplémentaires sont ajoutés .
    • L'ajout de propriétés nécessite l'ajout d'une seule fonction.
    • Si vous ajoutez un scénario, l'activation des avertissements du compilateur à propos des cas enum non gérés dans les instructions switch capturera les getters de propriété qui ne gèrent pas ce scénario.
  • Si vous devez modifier les propriétés booléennes de manière dynamique, vous n'avez pas besoin de valider à nouveau leurs combinaisons partout. Au lieu de basculer entre des indicateurs booléens individuels (ce qui pourrait entraîner des combinaisons d'indicateurs non valides), vous auriez plutôt une machine à états qui passe d'un scénario à un autre.

Cette approche a aussi l'avantage d'être très efficace.

1
jamesdlin

Plusieurs réponses correctes ont été données à cette question, mais j’adopterais un point de vue différent: si le code semble trop compliqué, il ya quelque chose qui ne va pas du tout . Le code sera difficile à déboguer et plus susceptible d'être "à usage unique".

Dans la vraie vie, quand on trouve une situation comme celle-ci:

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

Lorsque quatre états sont reliés par un motif aussi précis, nous traitons de la configuration d’une "entité" dans notre modèle

Une métaphore extrême est la manière dont nous décririons un "être humain" dans un modèle, si nous n'étions pas conscients de son existence en tant qu’entités unitaires avec des composants liés à des degrés de liberté spécifiques: nous devrions décrire des états indépendants de "torses", "bras", "jambes" et "tête", ce qui compliquerait la compréhension du système décrit. Un résultat immédiat serait des expressions booléennes anormalement compliquées.

De toute évidence, le moyen de réduire la complexité est l'abstraction et un outil de choix en c ++ est le paradigme object .

La question est donc: pourquoi existe-t-il un tel schéma? Qu'est-ce que c'est et que représente-t-il?

Comme nous ne connaissons pas la réponse, nous pouvons nous rabattre sur une abstraction mathématique: le array : nous avons trois scénarios, chacun étant maintenant un tableau.

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

A quel point vous avez votre configuration initiale. comme un tableau. Par exemple. std::array a un opérateur d'égalité:

A quel point votre syntaxe devient:

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

}

Tout comme la réponse de Gian Paolo, il est court, clair et facilement vérifiable/débogable. Dans ce cas, nous avons délégué les détails des expressions booléennes au compilateur.

1
fralau

La réponse acceptée est acceptable lorsque vous n’avez que 3 cas et que la logique de chacun est simple.

Mais si la logique de chaque cas était plus compliquée ou s'il y en avait beaucoup plus, une meilleure option consiste à utiliser le modèle de conception chaîne de responsabilité .

Vous créez une BaseValidator qui contient une référence à une BaseValidator et une méthode à validate et une méthode pour appeler la validation sur le validateur référencé.

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

Ensuite, vous créez un certain nombre de sous-classes héritant de BaseValidator, en remplaçant la méthode validate par la logique nécessaire pour chaque validateur.

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

Ensuite, l’utiliser est simple, instanciez chacun de vos validateurs et définissez chacun d’eux comme étant la racine des autres:

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

En substance, chaque cas de validation a sa propre classe qui est chargée de (a) déterminer si la validation correspond à ce cas, et (b) d’envoyer la validation à une autre personne de la chaîne si ce n’est pas le cas.

Veuillez noter que je ne suis pas familier avec C++. J'ai essayé de faire correspondre la syntaxe de certains exemples trouvés en ligne, mais si cela ne fonctionne pas, traitez-le plutôt comme un pseudocode. J'ai également un exemple de travail complet en Python ci-dessous qui peut être utilisé comme base si vous le souhaitez.

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

Encore une fois, vous constaterez peut-être que ce processus est excessif, mais il crée un code bien plus propre si vous vous retrouvez avec un ensemble de cas bien plus compliqué à respecter.

0
Jim Cullen

Mes 2 centimes: déclarer une somme variable (entier) telle que 

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

Vérifiez la somme par rapport aux conditions que vous voulez et c'est tout ..____ De cette façon, vous pourrez facilement ajouter d'autres conditions à l'avenir, en le gardant assez simple à lire.

0
SCdev