web-dev-qa-db-ja.com

4つのブール値がいくつかのケースに一致するかどうかをチェックするためのロジックを改善する方法

4つのbool値があります。

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

許容値は次のとおりです。

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

したがって、たとえば、このシナリオは受け入れられません。

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

現時点で私は悪いシナリオを検出するためにこのifステートメントを思い付きました:

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

そのステートメントロジックは改善/単純化できますか?

115
Andrew Truckle

私は読みやすさを目指します:あなたはたった3つのシナリオを持っています、3つの別々のifでそれらを扱います:

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

読みやすく、デバッグしやすい、私見。また、whichScenarioを処理しながら変数ifを代入することもできます。

たった3つのシナリオでは、「最初の3つの値が真であれば4番目の値をチェックすることを避けることができます」というようなことはしないでしょう。

エレガントな解決策ではありません 多分 確かに、しかしこの場合は大丈夫です:簡単で読みやすい。

ロジックがもっと複​​雑になったら、そのコードを捨てて、(Zladeckが示唆しているように)利用可能なさまざまなシナリオを保存するためにもっと何かを使うことを検討してください。

この答え に書かれた最初の提案は本当に大好きです。

(ほぼ)オフトピック:

私はここStackOverflowであまり答えを書いていません。上記の一般的な回答が私の歴史の中ではるかに高く評価されている回答(これまでに5〜10件以上の投票をしたことはありません)であることは本当に面白いです。

しかし、単純さは「正しい方法」であることが多く、多くの人がこれを考えているように思われます。

196
Gian Paolo

私はシンプルさと読みやすさを目指します。

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

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

シナリオの名前とフラグの名前を説明的なものに置き換えてください。それがあなたの特定の問題に意味があるならば、あなたはこの代替案を考慮することができます:

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

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

ここで重要なのは述語論理ではありません。それはあなたのドメインを説明し、あなたの意図を明確に表現しています。ここで重要なのは、すべての入力と中間変数に適切な名前を付けることです。正しい変数名が見つからない場合は、問題を間違った方法で説明している可能性があります。

121
Anders

Karnaugh map を使ってあなたのシナリオを論理式にすることができます。私は Online Karnaugh map ソルバーを4つの変数のための回路で使いました。

enter image description here

これにより、次のようになります。  

enter image description here

A, B, C, DbValue1, bValue2, bValue3, bValue4に変更してください。これは他にはありません: 

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

それであなたのifステートメントは次のようになります。

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • カルノーマップは、trueを評価すべき変数や条件がたくさんある場合に特に便利です。 
  • trueシナリオを論理式に整理した後、trueシナリオを示す適切なコメントを追加することをお勧めします。
104
P.W

ここでの本当の問題は、数ヶ月後に他の開発者(あるいは作者でさえ)がこのコードを変更しなければならないときに何が起こるかです。

これをビットフラグとしてモデル化することをお勧めします。

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

より多くのシナリオやより多くのフラグがある場合、テーブルアプローチはフラグを使用するよりも読みやすく拡張性があります。新しいシナリオをサポートするには、テーブル内に別の行が1つだけ必要です。

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

私の以前の答えはすでに受け入れられた答えです、私はここに私が読みやすく、簡単でそしてこの場合将来の修正に開かれていると思う何かを加えます:

@ ZdeslavVojkovicの回答(私はかなり良いと思う)から始めて、私はこれを思い付きました:

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

職場でそれを参照してください ここ

それが、私が通常目指している「洗練された保守可能な」(IMHO)ソリューションですが、実際のところ、OPの場合、以前の「たくさんのif」の答えは、たとえそれがエレガントでも保守可能でもなくても、OP要件を満たします。

28
Gian Paolo

また、他のアプローチを提出したいと思います。

私のアイデアは、ブール値を整数に変換してから、可変個引数テンプレートを使用して比較することです。

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

このシステムが最大32個のブールを入力としてサポートできることに注意してください。 unsignedunsigned long long(またはuint64_t)に置き換えると、サポートが64ケースに増えます。 if (summary != 0b1111u && summary != 0b1110u && summary != 0b1000u)が気に入らない場合は、さらに別の可変引数テンプレートメソッドを使用することもできます。

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

これが単純化されたバージョンです:

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

もちろん、この解決策は元の解決策よりも難読化されているので、その意味を理解するのは難しいかもしれません。


更新:コメントのMSaltersはもっと簡単な表現を見つけました:

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

テーブルをできるだけ直接プログラムに変換することを検討してください。ロジックで模倣するのではなく、テーブルからプログラムを実行します。

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

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

これは、可能な限り直接あなたの真理値表をコンパイラにエンコードします。

実例

std::any_ofを直接使用することもできます。

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

コンパイラはコードをインライン化し、反復を排除して独自のロジックを構築することができます。一方、あなたのコードはあなたが問題をどのように理解したかを正確に反映しています。

誰かが私の解決策を示すことを提案したコメントのように私はここに私の答えを提供するだけです。皆さんの洞察に感謝します。

最後に、3つの新しい「シナリオ」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));
}

それから私はこれらの私の検証ルーチンをこのように適用することができました:

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

私のライブアプリケーションでは、4つのbool値は実際には4つの値がエンコードされているDWORDから抽出されます。

みんなありがとう。

11
Andrew Truckle

シナリオの名前を挙げようとしている答えが表示されていませんが、OPのソリューションではまったく同じです。

私にとっては、各シナリオが何であるかのコメントを変数名または関数名のいずれかにカプセル化するのが最善です。名前よりもコメントを無視する可能性が高く、将来的にロジックが変更された場合は、コメントよりも名前を変更する可能性が高くなります。コメントをリファクタリングすることはできません。

あなたの関数の外でこれらのシナリオを再利用することを計画しているなら(あるいはそうしたいと思うかもしれません)、それからそれが評価するものを言う関数を作りなさい( constexpr / noexcept optionalしかし推奨)

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

可能であればこれらのクラスメソッドを作成してください(OPのソリューションのように)。ロジックを再利用したくない場合は、関数内で変数を使用できます。

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;

コンパイラは、bValue1がfalseの場合、すべてのシナリオがfalseであることをおそらく整理します。速くすることを心配しないで、ただ正しく読みやすくする。あなたのコードをプロファイルして、これがボトルネックであることがわかったならば、コンパイラは-O2以上で最適以下のコードを生成したのでそれを書き直すようにしてください。

11
Erroneous

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

このアプローチは、有効な条件の数が増えても、それらをシナリオリストに追加するだけで拡張可能です。 

9
hessam hedieh

最初の2つのシナリオは似ていることに気付くのは簡単です - それらはほとんどの条件を共有しています。あなたが現在どのシナリオで自分がいるのかを選択したいのなら、このように書くことができます(それは修正された @ 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

さらに進むと、最初のブール値は常にtrueである必要があることに気付くでしょう。これは入力条件であるため、次のようになる可能性があります。

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
}

さらに、bValue2とbValue3は多少関連していることがはっきりとわかります。それらの状態をより適切な名前の外部関数または変数に抽出できます(これは必ずしも簡単ではないか適切ではありません)。

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
}

このようにすると、いくつかの利点と欠点があります。

  • 条件が小さいので、それについて推論する方が簡単です。
  • これらの条件をよりわかりやすくするには、ニースの名前を変更する方が簡単です。
  • しかし、彼らは範囲を理解する必要があります、 
  • さらにそれはより堅い

上記のロジックに変更があると予測した場合は、 @ gian-paolo で示されているように、より直接的なアプローチを使用する必要があります。

そうでなければ、もしこれらの条件が十分に確立されていて、そして決して変わることのない一種の「堅実な規則」であるならば、私の最後のコードスニペットを考えてください。

9
Michał Łoś

すべての答えは非常に複雑で読みにくいです。これに対する最善の解決策はswitch()ステートメントです。読みやすく、追加のケースの追加や修正も簡単です。コンパイラは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;
}

さらに読みやすくするために、caseステートメントで定数とORを一緒に使用することもできます。

7
shogged

Mchによって示唆されているように、あなたはすることができます:

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

最初の行が最初の2つの良いケースをカバーし、2行目が最後のケースをカバーします。

Live Demo、私が遊んだところ、それはあなたのケースを通過します。

7
gsamaras

@ GianPaoloの良い答えに対する若干のバリエーション。読みやすくすることができます。

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

わかりやすくするために、ショートカット変数も使用します。前述のように、bValue4の値がこれら2つのシナリオの真実に影響を与えないため、シナリオ1はシナリオ2と同じです。

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

それからあなたの表現は始まる:

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

意味のある名前をMAJORTRUE変数とMAJORFALSE変数に(実際にはbValue * varsにも)与えることは、読みやすさと保守性に非常に役立ちます。

6
Gnudiff

特定の「if」文ではなく、問題の読みやすさに焦点を当てます。

これにより多くのコード行が生成されますが、やり過ぎや不要と見なされることもあります。読みやすさを維持するには、特定の論理値からシナリオを抽出するのが最善の方法です。

わかりやすい名前で物事をクラスに分割することで(関数だけを使用すること、または他の任意のツールを自由に使用することによって) - 各シナリオの背後にある意味をはるかに簡単に示すことができます。さらに重要なことに、可動部分が多いシステムでは - 既存のシステムへの保守と参加がより簡単です(ここでも、追加のコードが呼び出される量にかかわらず)。

#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

それが何を表すかによって異なります

たとえば、1がキーで、23が一致する必要がある2人の場合(NOTname__に同意する場合を除く)4 - 確認のため)最も読みやすいものは次のようになります。

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

人気の要求によって:

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

ビット単位の操作を実行すると、非常にきれいでわかりやすく見えます。 

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

わかりやすくするためにa、b、c、dを表し、補完するためにA、B、C、Dを表す

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

方程式

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

あなたに合った方程式を使ってください。

3
yumoji

受け入れられた答えに対する単なる個人的な好み、しかし私は書くでしょう:

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
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1は常に真でなければならない 
  • b2は常にb3と等しくなければなりません 
  • b2(とb3)が真ならば、b4は偽にはなり得ない

単純な

3
Owen Meyer

まず、シナリオチェックのみを変更できると仮定して、読みやすさに焦点を当て、チェックを関数でラップして、if(ScenarioA())を呼び出すことができるようにします。


さて、あなたが実際にこれを最適化する必要がある/必要であると仮定して、私は密接にリンクされたBooleansを定数整数に変換し、それらにビット演算子を使用することをお勧めします

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
}

これにより、シナリオの一部をリストするのと同じくらい簡単にシナリオを表現できます。また、switchステートメントを使用して正しい条件にジャンプし、これまで見たことがない他の開発者を混乱させることができます。 (C#RegexOptionsはこのパターンをフラグの設定に使用します。c++ライブラリの例があるかどうかはわかりません)

2
Tezra

入れ子にされたifsは何人かの人々にとって読みやすくなるでしょう。これが私のバージョンです

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

あなたがブールフラグを取り除くならば、あなたはブールフラグの無効な組み合わせについて心配する必要はありません。

許容値は次のとおりです。

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

あなたは明らかに3つの状態(シナリオ)を持っています。それをモデル化し、 これらの状態から ブール値のプロパティを派生させる方が良いでしょう。

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

これは Gian Paoloの答え よりも間違いなくコードの数が多いです。しかしあなたの状況によっては、これははるかに保守しやすいでしょう:

  • 追加のブール値プロパティまたはシナリオが追加された場合に変更するための関数の中心的なセットがあります。
    • プロパティを追加するには、単一の関数を追加するだけで済みます。
    • シナリオを追加する場合、enumステートメントで未処理のswitchケースに関するコンパイラ警告を有効にすると、そのシナリオを処理しないプロパティ取得メソッドが捕捉されます。
  • ブール値のプロパティを動的に変更する必要がある場合は、どこでもそれらの組み合わせを再検証する必要はありません。個々のブールフラグを切り替える(フラグの組み合わせが無効になる可能性がある)代わりに、あるシナリオから別のシナリオに移行するステートマシンを使用することになります。

このアプローチには、非常に効率的であるという副次的な利点もあります。

1
jamesdlin

この質問に対していくつかの正しい答えが出されていますが、私は別の見方をします: コードが複雑すぎるように見える場合、何かがまったく正しくない コードはデバッグが難しくなり、「使い捨て」になる可能性が高くなります。

実生活では、次のような状況が見られます。

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

4つの状態がこのような正確なパターンで接続されている場合、 私たちのモデルでは何らかの "エンティティ"の構成を扱っています 。 

極端な比喩は、特定の自由度に接続された要素を持つ単一の実体としての存在を意識していなければ、モデルの中で「人間」をどのように表現するかです。 「腕」、「脚」および「頭」は、説明されたシステムを理解するのを複雑にするだろう。 即座の結果は不自然に複雑なブール式になるでしょう。

明らかに、複雑さを軽減する方法は抽象化であり、c ++で最適なツールは オブジェクトパラダイム です。

だから質問は: なぜ そのようなパターンはありますか?これは何ですか、それは何を表しますか?

答えがわからないので、数学的抽象化に頼ることができます。 配列 :3つのシナリオがあり、それぞれが配列になっています。

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

どの時点であなたはあなたの初期設定を持っています。配列として。例えば。 std::arrayには等価演算子があります。

どの時点で構文は次のようになります。

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

}

Gian Paoloによる答えと同じように、短く、明確で、簡単に検証可能/デバッグ可能です。この場合、ブール式の詳細をコンパイラに委任しました。

1
fralau

あなたがたった3つのケースを持っていて、そしてそれぞれのための論理が簡単であるところで受け入れられた答えは素晴らしいです。

しかし、各ケースのロジックがもっと複​​雑な場合、またはもっと多くのケースがある場合は、 一連の責任 設計パターンを使用することをお勧めします。

BaseValidatorへの参照とBaseValidatorへのメソッド、および参照されているバリデータの検証を呼び出すメソッドを含むvalidateを作成します。

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

それからBaseValidatorから継承したサブクラスをいくつか作成し、各バリデーターに必要なロジックでvalidateメソッドをオーバーライドします。

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

それを使用するのは簡単です、あなたのバリデータのそれぞれをインスタンス化して、それらのそれぞれを他のもののルートになるように設定してください:

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

本質的には、各検証ケースは、(a)検証が that caseに一致するかどうかを判断し、(b)検証がチェーン内の他の誰かに一致しない場合に送信する責任があります。

私はC++に慣れていないことに注意してください。オンラインで見つけたいくつかの例の構文を一致させようとしましたが、これがうまくいかない場合は、疑似コードのように扱います。私はまた、必要ならば基礎として使用することができる完全な実用的なPythonの例を以下に示します。

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

繰り返しになりますが、この例ではやり過ぎるかもしれませんが、はるかに複雑な一連の条件を満たす必要がある場合は、よりクリーンなコードが作成されます。

0
Jim Cullen

私の2セント:変数sum(integer)を宣言して、 

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

あなたが望む条件に対して合計をチェックすればそれで終わりです。こうすれば、将来もっと簡単に条件を追加して、読みやすくなります。

0
SCdev