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


118

J'ai quatre boolvaleurs:

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, j'ai élaboré cette ifdéclaration pour 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 de déclaration peut-elle être améliorée / simplifiée?


8
J'utiliserais une table au lieu d'une ifdéclaration complexe . De plus, comme il s'agit d'indicateurs booléens, vous pouvez modéliser chaque scénario en tant que constante et la comparer.
Zdeslav Vojkovic

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

14
quels sont les scénarios en fait? Souvent, les choses deviennent beaucoup plus simples si vous donnez simplement des noms propres aux choses, par exemplebool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
idclev 463035818

6
En utilisant des noms significatifs, vous pouvez extraire chaque condition complexe dans une méthode et appeler cette méthode dans if condition. Ce serait beaucoup plus lisible et maintenable. Par exemple, jetez un œil à l'exemple fourni dans le lien. refactoring.guru/decompose-conditional
Hardik Modha

Réponses:


195

Je viserais la lisibilité: vous n'avez que 3 scénarios, traitez-les avec 3 ifs distincts:

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 whichScenariotout en poursuivant le if.

Avec seulement 3 scénarios, je n'irais pas avec quelque chose comme "si les 3 premières valeurs sont vraies, je peux éviter de vérifier la quatrième valeur": cela va rendre 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 Zladeck le suggère).

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 sur 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 (je n'ai jamais eu plus de 5 à 10 votes positifs avant je pense) alors qu'en fait ce n'est pas ce que je pense généralement être la «bonne» façon de le faire.

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


1
sûr @hessamhedieh, ce n'est correct que pour un petit nombre de scénarios disponibles. comme je l'ai dit, si les choses se compliquent, mieux vaut chercher autre chose
Gian Paolo

4
Cela peut être simplifié davantage en empilant toutes les conditions dans l'initialiseur pour validet en les séparant par ||, plutôt que de muter validdans des blocs d'instructions séparés. Je ne peux pas mettre d'exemple dans le commentaire mais vous pouvez aligner verticalement les ||opérateurs le long de la gauche pour que cela soit très clair; les conditions individuelles sont déjà mises entre parenthèses autant qu'elles le doivent (pour if), vous n'avez donc pas besoin d'ajouter de caractères aux expressions au-delà de ce qui existe déjà.
Leushenko

1
@Leushenko, je pense que mélanger les parenthèses, && et || les conditions sont assez sujettes aux erreurs (quelqu'un dans une réponse différente a dit qu'il y avait une erreur entre parenthèses dans le code dans OP, peut-être qu'elle a été corrigée). Un bon alignement peut aider, bien sûr. Mais quel est l'avantage? plus lisible? plus facile à entretenir? Je ne pense pas. Juste mon avis bien sûr. Et soyez sûr, je déteste vraiment avoir beaucoup de ifs dans le code.
Gian Paolo

3
Je l'aurais enveloppé dans un if($bValue1)car cela doit toujours être vrai, permettant techniquement une amélioration mineure des performances (bien que nous parlions ici de quantités négligeables).
Martijn

2
FWIW: il n'y a que 2 scénarios: les 2 premiers sont le même scénario et ne dépendent pas debValue4
Dancrumb

123

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 a du sens pour 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, ce n'est pas la logique des prédicats. Il décrit votre domaine et exprime clairement votre intention. La clé ici est de donner de bons noms à toutes les entrées et variables intermédiaires. Si vous ne trouvez pas de bons noms de variables, cela peut être le signe que vous décrivez le problème de la mauvaise manière.


3
+1 C'est ce que j'aurais fait aussi. Tout comme @RedFilter le souligne, et contrairement à la réponse acceptée, c'est auto-documenté. Donner aux scénarios leurs propres noms dans une étape distincte est beaucoup plus lisible.
Andreas

106

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

entrez la description de l'image ici

Cela donne:

entrez la description de l'image ici

Changer A, B, C, Dpour bValue1, bValue2, bValue3, bValue4, ce n'est rien d'autre que:

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

Ainsi, votre ifdéclaration devient:

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Les cartes de Karnaugh sont particulièrement utiles lorsque vous avez de nombreuses variables et de nombreuses conditions à évaluer true.
  • Après avoir réduit les truescénarios à une équation logique, ajouter des commentaires pertinents indiquant les truescénarios est une bonne pratique.

96
Bien que techniquement correct, ce code nécessite beaucoup de commentaires afin d'être édité par un autre développeur quelques mois plus tard.
Zdeslav Vojkovic

22
@ZdeslavVojkovic: Je voudrais simplement ajouter un commentaire avec l'équation. //!(ABC + AB'C'D') (By K-Map logic). Ce serait un bon moment pour le développeur d'apprendre K-Maps s'il ne les connaît pas déjà.
PW

11
Je suis d'accord avec cela, mais le problème à l'OMI est qu'il ne correspond pas clairement au domaine du problème, c'est-à-dire comment chaque condition correspond à un scénario spécifique, ce qui rend difficile le changement / l'extension. Qu'est - ce qui se passe quand il y a Eet Fconditions et 4 nouveaux scénarios? Combien de temps faut-il pour mettre à jour ifcorrectement cette déclaration? Comment la revue de code vérifie-t-elle si elle est correcte ou non? Le problème n'est pas du côté technique mais du côté "business".
Zdeslav Vojkovic

7
Je pense que vous pouvez prendre en compte A: ABC + AB'C'D' = A(BC + B'C'D')(cela peut même être pris en compte même A(B ^ C)'(C + D')si je ferais attention d'appeler cela «simplification»).
Maciej Piechotka

28
@PW Ce commentaire semble à peu près aussi compréhensible que le code, et est donc un peu inutile. Un meilleur commentaire expliquerait comment vous avez réellement trouvé cette équation, c'est-à-dire que l'instruction doit déclencher pour TTTT, TTTF et TFFF. À ce stade, vous pourriez aussi bien écrire ces trois conditions dans le code à la place et ne pas avoir besoin d'explication du tout.
Bernhard Barker

58

La vraie question ici est: que se passe-t-il lorsqu'un autre développeur (ou même auteur) doit changer ce code quelques mois plus tard.

Je suggérerais de modéliser cela sous forme d'indicateurs 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 de table est plus lisible et extensible que l'utilisation d'indicateurs. La prise en charge d'un nouveau scénario ne nécessite qu'une autre ligne dans le tableau.

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
Ce n'est pas le plus maintenable mais simplifie définitivement la condition if. Donc, laisser quelques commentaires sur les opérations au niveau du bit sera une nécessité absolue ici imo.
Adam Zahran

6
OMI, le tableau est la meilleure approche car il s'adapte mieux avec des scénarios et des indicateurs supplémentaires.
Zdeslav Vojkovic

J'aime votre première solution, facile à lire et modifiable. Je ferais 2 améliorations: 1: attribuer des valeurs à scenarioX avec une indication explicite des valeurs booléennes utilisées, par exemple SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;2: éviter les variables SCENARIO_X puis stocker tous les scénarios disponibles dans un fichier <std::set<int>. L'ajout d'un scénario va être juste quelque chose comme mySet.insert( true << 3 | false << 2 | true << 1 | false;peut-être un peu exagéré pour seulement 3 scénarios, OP a accepté la solution rapide, sale et facile que j'ai suggérée dans ma réponse.
Gian Paolo

4
Si vous utilisez C ++ 14 ou supérieur, je suggérerais plutôt d'utiliser des littéraux binaires pour la première solution - 0b1111, 0b1110 et 0b1000 est beaucoup plus clair. Vous pouvez probablement également simplifier un peu cela en utilisant la bibliothèque standard ( std::find?).
Bernhard Barker

2
Je trouve que les littéraux binaires ici seraient une exigence minimale pour rendre le premier code propre. Dans sa forme actuelle, c'est complètement cryptique. Les identifiants descriptifs peuvent aider, mais je n'en suis même pas sûr. En fait, les opérations sur les bits pour produire la scenariovaleur me semblent inutilement sujettes à des erreurs.
Konrad Rudolph

27

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

En commençant par la réponse @ZdeslavVojkovic (que je trouve assez bonne), j'ai trouvé 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;
}

Voyez-le au travail ici

Eh bien, c'est la solution "élégante et maintenable" (IMHO) que je vise habituellement, mais vraiment, pour le cas OP, ma précédente réponse "bunch of ifs" correspond mieux aux exigences OP, même si elle n'est ni élégante ni maintenable.


Vous savez que vous pouvez toujours modifier votre réponse précédente et apporter des améliorations.
Andreas le

20

Je voudrais également proposer une autre approche.

Mon idée est de convertir les booléens en un entier, puis de 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. remplacer le unsignedpar unsigned long long(ou uint64_t) augmente la prise en charge à 64 cas. Si vous n'aimez pas le 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
    }
}

2
J'adore cette approche, sauf pour le nom de la fonction principale: "from bool ... to what ?" - Pourquoi pas explicitement bitmap_from_bools, ou bools_to_bitmap?
Konrad Rudolph

oui @KonradRudolph, je ne pourrais pas penser à un meilleur nom, sauf peut-être bools_to_unsigned. Bitmap est un bon mot-clé; édité.
Stack Danny

Je pense que tu veux summary!= 0b1111u &&.... a != b || a != cest toujours vrai sib != c
MooseBoys

17

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 l'original, sa signification peut être plus difficile à comprendre.


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

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

1
Oui, mais difficile à comprendre. Mais merci pour la suggestion.
Andrew Truckle

J'ai comparé la capacité des compilateurs à simplifier l'expression avec votre simplification comme référence: l' explorateur de compilateurs . gcc n'a pas trouvé votre version optimale mais sa solution est toujours bonne. Clang et MSVC ne semblent pas effectuer de simplification d'expression booléenne.
Oliv

1
@AndrewTruckle: notez que si vous aviez besoin d'une version plus lisible, veuillez le dire. Vous avez dit «simplifié», mais vous acceptez une version encore plus détaillée que la vôtre.
geza

1
simpleest en effet un terme vague. Beaucoup de gens le comprennent dans ce contexte comme plus simple à comprendre pour le développeur et non pour le compilateur pour générer du code, donc plus détaillé peut en effet être plus simple.
Zdeslav Vojkovic

1
@IsmaelMiguel: lorsqu'une formule logique est optimisée pour un nombre de termes, la signification originale est généralement perdue. Mais on peut mettre un commentaire autour de lui, donc ce qu'il fait est clair. Même, pour la réponse acceptée, un commentaire ne ferait pas de mal.
geza

12

Pensez à traduire vos tableaux aussi directement que possible dans votre programme. Conduisez le programme hors de la table, au lieu de l'imiter avec la 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;
}

maintenant

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

cela encode directement que possible votre table de vérité dans le compilateur.

Exemple en direct .

Vous pouvez également utiliser std::any_ofdirectement:

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 incorporer le code, éliminer toute itération et créer sa propre logique pour vous. Pendant ce temps, votre code reflète exactement comment vous avez conçu le problème.


La première version est si facile à lire et si maintenable, je l'aime vraiment. Le second est plus difficile à lire, du moins pour moi, et nécessite un niveau de compétence C ++ peut-être au-dessus de la moyenne, sûrement au-dessus du mien. Ce n'est pas quelque chose que tout le monde est capable d'écrire. Je viens d'apprendre quelque chose de nouveau, merci
Gian Paolo

11

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

Au final, j'ai choisi d'ajouter trois nouvelles booleanméthodes «scénario» :

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 de ma routine de validation comme ceci:

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

Dans mon application en direct, les 4 valeurs booléennes sont en fait extraites de a DWORDqui contient 4 valeurs encodées.

Merci encore à tous.


1
Merci d'avoir partagé la solution. :) C'est en fait mieux que le complexe si les conditions de l'enfer Peut-être que vous pouvez encore INCLUDE_ITEM1mieux nommer etc. et que vous êtes tous bons. :)
Hardik Modha

1
@HardikModha Eh bien, techniquement, ce sont des "éléments d'étudiant" et le drapeau est pour indiquer s'ils doivent être "inclus". Je pense donc que le nom, même s'il semble générique, est en fait significatif dans ce contexte. :)
Andrew Truckle

11

Je ne vois aucune 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 à l'avenir, vous êtes plus susceptible de changer un nom qu'un commentaire. Vous ne pouvez pas refactoriser un commentaire.

Si vous prévoyez de réutiliser ces scénarios en dehors de votre fonction (ou si vous le souhaitez), créez une fonction qui indique ce qu'elle évalue ( constexpr/ noexceptfacultatif 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 d'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 réglera très probablement que si bValue1 est faux, tous les scénarios sont faux. Ne vous inquiétez pas de le rendre rapide, juste correct et lisible. Si vous profilez votre code et trouvez qu'il s'agit d'un goulot d'étranglement parce que le compilateur a généré du code sous-optimal à -O2 ou supérieur, essayez de le réécrire.


J'aime un peu plus cette solution (déjà sympa) de Gian Paolo: elle évite le flux de contrôle et l'utilisation d'une variable qui est écrasée - un style plus fonctionnel.
Dirk Herrmann

9

Manière 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;
}

Cette approche est évolutive comme si le nombre de conditions valides augmentait, vous en ajoutiez facilement plus à la liste des scénarios.


Je suis presque sûr que c'est faux, cependant. Il suppose que le compilateur n'utilise qu'une seule représentation binaire pour true. Un compilateur qui utilise «tout ce qui n'est pas nul est vrai» provoque l'échec de ce code. Notez que truedoit être converti en 1, il n'a tout simplement pas besoin d'être stocké en tant que tel.
MSalters

@MSalters, tnx, je comprends votre point et je suis conscient de cela, un peu comme 2 is not equal to true but evaluates to true, mon code ne force pas int 1 = trueet fonctionne tant que tous les vrais sont convertis en la même valeur int, alors voici ma question: Pourquoi le compilateur devrait-il agir au hasard lors de la conversion fidèle à l'int sous-jacent, pouvez-vous s'il vous plaît élaborer plus?
hessam hedieh

Effectuer un memcmptest de conditions booléennes n'est pas la méthode C ++, et je doute plutôt que ce soit une méthode C établie non plus.
Konrad Rudolph

@hessamhedieh: Le problème dans votre logique est de "convertir vrai en int sous-jacent". Ce n'est pas ainsi que les compilateurs fonctionnent,
MSalters

Votre code augmente la complexité de O (1) à O (n). Pas une manière d'aller dans n'importe quel langage - laissez de côté C / C ++.
mabel

9

Il est facile de remarquer que les deux premiers scénarios sont similaires - ils partagent la plupart des conditions. Si vous voulez sélectionner dans quel scénario vous vous trouvez en ce moment, 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 pouvez remarquer que le premier booléen doit être toujours vrai, ce qui est une condition d'entrée, vous pouvez donc 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 clairement voir que bValue2 et bValue3 sont quelque peu connectés - vous pouvez extraire leur état vers certaines fonctions externes ou variables avec un nom plus approprié (ce n'est pas toujours facile ou approprié cependant):

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
}

Faire de cette façon 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 pour rendre ces conditions plus compréhensibles,
  • mais, ils nécessitent 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 devriez utiliser une approche plus simple comme présentée par @ gian-paolo .

Sinon, si ces conditions sont bien établies, et sont des sortes de «règles solides» qui ne changeront jamais, considérez mon dernier extrait de code.


7

Comme suggéré par mch, vous pouvez 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 tes valises.


7

Une légère variation sur la bonne réponse de @ GianPaolo, que certains peuvent trouver 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

Chaque réponse est trop complexe et difficile à lire. La meilleure solution à cela est une switch()déclaration. Il est à la fois lisible et facilite l'ajout / la modification de cas supplémentaires. Les compilateurs sont également bons pour optimiser les switch()déclarations.

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 des constantes et les OU ensemble dans les caseinstructions pour une meilleure lisibilité.


Étant un ancien programmeur C, je définirais une macro "PackBools" et l'utiliserais à la fois pour le "switch (PackBools (a, b, c, d))" et pour les cas, par exemple soit directement "case PackBools (true , true ...) "ou définissez-les comme constantes locales.eg" const unsigned int scenario1 = PackBools (true, true ...); "
Simon F

6

J'utiliserais également 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'en fait à bValue * vars) aiderait beaucoup à la lisibilité et à la maintenance.


6

Concentrez-vous sur la lisibilité du problème, pas sur l'énoncé «si» spécifique.

Bien que cela produise plus de lignes de code, et certains peuvent le considérer comme excessif ou inutile. Je suggérerais que l'abstraction de vos scénarios à partir des booléens spécifiques est le meilleur moyen de maintenir la lisibilité.

En divisant les choses en classes (n'hésitez pas à utiliser simplement des fonctions, ou tout autre outil que vous préférez) avec des noms compréhensibles - nous pouvons beaucoup plus facilement montrer la signification de chaque scénario. Plus important encore, dans un système avec de nombreuses pièces mobiles - il est plus facile de maintenir et de se joindre à vos systèmes existants (encore une fois, malgré la quantité de code supplémentaire impliquée).

#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
À un moment donné, la verbosité commence à nuire à la lisibilité. Je pense que cela va trop loin.
JollyJoker

2
@JollyJoker Je suis en fait d'accord dans cette situation spécifique - cependant, mon instinct de la façon dont OP a tout nommé de manière extrêmement générique, est que leur "vrai" code est probablement beaucoup plus complexe que l'exemple qu'ils ont donné. Vraiment, je voulais juste mettre cette alternative là-bas, car c'est ainsi que je la structurerais pour quelque chose de beaucoup plus complexe / impliqué. Mais vous avez raison - pour l'exemple spécifique des OP, c'est trop verbeux et aggrave les choses.

5

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 être d'accord (sauf si elles sont d'accord sur le fait NOTqu'elles ont besoin d'une troisième personne - 4 - pour confirmer), la plus lisible pourrait être:

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

à la demande générale:

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

2
Vous avez peut-être raison, mais l'utilisation de chiffres pour illustrer votre argument nuit à votre réponse. Essayez d'utiliser des noms descriptifs.
jxh

1
@jxh Ce sont les nombres OP utilisés. Je viens de supprimer le fichier bValue.
ispiro

@jxh J'espère que c'est mieux maintenant.
ispiro

4

Faire une opération au niveau du bit 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
}

1
La comparaison au niveau du bit me semble lisible. La composition, en revanche, semble artificielle.
xtofl

3

Je désigne a, b, c, d pour plus de clarté et A, B, C, D pour les 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 toutes les équations qui vous conviennent.


3
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

Facile


3

Juste une préférence personnelle sur la réponse acceptée, mais j'écrirais:

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

Tout d'abord, en supposant que vous ne pouvez modifier que la vérification du scénario, je me concentrerais sur la lisibilité et j'envelopperais simplement la vérification dans une fonction afin que vous puissiez simplement appeler if(ScenarioA()).


Maintenant, en supposant que vous voulez / devez réellement optimiser cela, je recommanderais de convertir les booléens étroitement liés en nombres entiers constants et d'utiliser des opérateurs de bits sur eux

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 rend l'expression des scénarios aussi simple que de lister ce qui en fait partie, vous permet d'utiliser une instruction switch pour passer à la bonne condition et de dérouter 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 ++)


En fait, je n'utilise pas quatre valeurs booléennes mais un DWORD avec quatre BOOLS incorporés. Trop tard pour le changer maintenant. Mais merci pour votre suggestion.
Andrew Truckle

2

Les ifs imbriqués pourraient être plus faciles à 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;
}

Personnellement, j'éviterais généralement d'imbriquer des déclarations si possible. Bien que ce cas soit agréable et lisible, une fois que de nouvelles possibilités sont ajoutées, l'imbrication peut devenir très difficile à lire. Mais si les scénarios ne changent jamais, c'est définitivement une solution agréable et lisible.
Dnomyar96

@ Dnomyar96 je suis d'accord. Personnellement, j'évite aussi les si imbriqués. Parfois, si la logique est compliquée, il m'est plus facile de comprendre la logique en la décomposant en morceaux. Par exemple, une fois que vous entrez dans le bValue1bloc, vous pouvez tout traiter comme une nouvelle page fraîche dans votre processus mental. Je parie que la manière d'aborder le problème peut être très personnelle ou même culturelle.
sardok

1

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é, quelque chose ne va pas . 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 modèle aussi précis, nous avons affaire à la configuration d'une «entité» dans notre modèle .

Une métaphore extrême est de savoir comment nous décririons un "être humain" dans un modèle, si nous n'étions pas conscients de leur existence en tant qu'entités unitaires avec des composants connectés à des degrés de liberté spécifiques: il faudrait 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 objet .

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

Comme on ne connaît pas la réponse, on peut se rabattre sur une abstraction mathématique: le tableau : on a trois scénarios, dont chacun est 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

À quel point vous avez votre configuration initiale. comme un tableau. Par exemple, std::arraya un opérateur d'égalité:

À 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, elle est courte, claire et facilement vérifiable / débuggable. Dans ce cas, nous avons délégué les détails des expressions booléennes au compilateur.


1

Vous n'aurez pas à vous soucier des combinaisons invalides d'indicateurs booléens si vous vous débarrassez des indicateurs booléens.

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

  • Il existe un ensemble central de fonctions à modifier si des propriétés booléennes ou des scénarios 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 sur les enumcas non gérés dans les switchinstructions interceptera les acquéreurs de propriétés qui ne gèrent pas ce scénario.
  • Si vous avez besoin de modifier les propriétés booléennes de manière dynamique, vous n'avez pas besoin de revalider leurs combinaisons partout. Au lieu de basculer des indicateurs booléens individuels (ce qui pourrait entraîner des combinaisons d'indicateurs non valides), vous auriez à la place une machine à états qui passe d'un scénario à un autre.

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


0

Mes 2 cents: déclarez une somme variable (entier) pour 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 souhaitez et c'est tout. De cette façon, vous pouvez facilement ajouter plus de conditions à l'avenir en le maintenant assez simple à lire.


0

La réponse acceptée est bonne lorsque vous n'avez que 3 cas et où la logique de chacun est simple.

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

Vous créez un BaseValidatorqui contient une référence à a BaseValidatoret une méthode à validateet 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 qui héritent de BaseValidator, en remplaçant la validateméthode 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, son utilisation est simple, instanciez chacun de vos validateurs et définissez chacun d'entre 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 responsable (a) de déterminer si la validation correspond à ce cas, et (b) d'envoyer la validation à quelqu'un d'autre dans 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 que j'ai trouvés en ligne, mais si cela ne fonctionne pas, traitez-le plus comme un pseudocode. J'ai également un exemple Python fonctionnel complet 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 pouvez trouver cette surpuissance pour votre exemple spécifique, mais cela crée un code beaucoup plus propre si vous vous retrouvez avec un ensemble de cas beaucoup plus compliqués qui doivent être remplis.


-2

Une approche simple consiste à trouver la réponse que vous jugez acceptable.

Oui = (booléen1 && booléen2 && booléen3 && booléen4) + + ...

Maintenant, si possible, simplifiez l'équation en utilisant l'algèbre booléenne.

comme dans ce cas, acceptable1 et 2 se combinent en (boolean1 && boolean2 && boolean3).

Par conséquent, la réponse finale est:

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

-3

utiliser le champ de bits :

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 :

C'est vraiment dommage pour les CPP. Mais, UB n'est pas mon souci, vérifiez-le sur http://coliru.stacked-crooked.com/a/2b556abfc28574a1 .


2
Cela provoque UB en raison de l'accès à un champ union inactif.
HolyBlackCat

Formellement, c'est UB en C ++, vous ne pouvez pas définir un membre d'union et lire à partir d'un autre. Techniquement, il pourrait être préférable d'implémenter des getters \ setters basés sur des modèles pour les bits de valeur intégrale.
Swift - Friday Pie

Je pense que le comportement passerait à la définition de la mise en œuvre si l'on convertissait l'adresse du syndicat en adresse unsigned char*, bien que je pense que le simple fait d'utiliser quelque chose comme ce ((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1serait probablement plus efficace.
supercat
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.