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


118

私には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
}

そのステートメントロジックを改善/簡略化できますか?


8
複雑なifステートメントの代わりにテーブルを使用します。さらに、これらはブールフラグであるため、各シナリオを定数としてモデル化し、それをチェックできます。
Zdeslav Vojkovic

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

14
実際のシナリオは何ですか?多くの場合、適切な名前を付けるだけで、物事はより簡単になります。例:bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
idclev 463035818

6
意味のある名前を使用して、複雑な各条件をメソッドに抽出し、if条件でそのメソッドを呼び出すことができます。より読みやすく、保守しやすくなります。たとえば、リンクで提供されている例を見てください。refactoring.guru/decompose-conditional
のHardik Modha

回答:


195

私は読みやすさを目指しています。シナリオは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

読みやすく、デバッグしやすい、IMHO。また、のwhichScenario処理中に変数を割り当てることができますif

たった3つのシナリオでは、「最初の3つの値がtrueの場合、4番目の値をチェックしないようにすることができます」というようなことはしません。コードの読み取りと保守が難しくなります。

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

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

私はこの答えで与えられた最初の提案が本当に大好きです:読みやすく、エラーが発生しにくく、保守可能

(ほぼ)オフトピック:

ここStackOverflowでは、あまり多くの答えを書きません。上記の受け入れられた回答が私の歴史でこれまでで最も高く評価された回答である(私が考える前に5〜10票を超えることは決してなかった)のは本当に面白いですが、実際には、私が通常は「正しい」方法であるとは考えていません。

しかし、単純さはしばしば「それを行う正しい方法」であり、多くの人々はこれを考えているようであり、私は私よりももっと考えるべきです:)


1
確かに@hessamhedieh、それは少数の利用可能なシナリオでのみ問題ありません。私が言ったように、物事がより複雑になったら、何か他のものを探すべきです
Gian Paolo

4
これは、個別のステートメントブロック内で変更validするので||はなく、すべての条件をイニシャライザにスタックし、でそれらを分離することにより、さらに簡略化できますvalid。コメントに例を示すことはできませんが||、左側に沿って演算子を垂直方向に揃えて、これを非常に明確にすることができます。個々の条件は、必要なだけ(if)のように括弧で囲まれているため、式に文字を追加する必要はありません。
ロイセンコ、

1
@ロイシェンコ、かっこ、&&、||を混ぜると思います 条件はかなりエラーを起こしやすい(別の答えの誰かが、OPのコードの括弧にエラーがあったと言った、おそらくそれは修正された)。適切な配置は確かに役立ちます。しかし、利点は何ですか?もっと読みやすい?維持しやすいですか?そうは思いません。もちろん私の意見です。確かに、コードにifがたくさんあるのは本当に嫌いです。
Gian Paolo

3
私はそれif($bValue1)を常にtrueにする必要があるので、それをラップしました。技術的には多少のパフォーマンスの向上を可能にします(ここでは無視できる量について話しています)。
Martijn、

2
FWIW:2つのシナリオしかありません:最初の2つは同じシナリオであり、依存しませんbValue4
Dancrumb

123

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

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

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


3
+1これも私がやったことです。@RedFilterが指摘するように、受け入れられた回答とは対照的に、これは自己文書化されています。別の手順でシナリオに独自の名前を付けると、はるかに読みやすくなります。
Andreas

105

Karnaughマップを使用して、シナリオを論理方程式に縮小できます。私は、4つの変数の回路を備えたオンラインカルノーマップソルバーを使用しました。

ここに画像の説明を入力してください

これにより、

ここに画像の説明を入力してください

に変更A, B, C, DするとbValue1, bValue2, bValue3, bValue4、これは他に何もありません。

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

したがって、ifステートメントは次のようになります。

if(!(bValue1 && bValue2 && bValue3 || bValue1 && !bValue2 && !bValue3 && !bValue4))
{
    // There is some error
}
  • Karnaughマップは、評価する必要のある変数や条件が多い場合に特に役立ちますtrue
  • trueシナリオを論理方程式に変換した後、シナリオを示す関連コメントを追加することをお勧めしますtrue

96
技術的には正しいものの、このコードは、数か月後に別の開発者が編集するために多くのコメントを必要とします。
Zdeslav Vojkovic

22
@ZdeslavVojkovic:方程式にコメントを追加します。//!(ABC + AB'C'D') (By K-Map logic)。開発者がK-Mapをまだ知らない場合は、この時点で学ぶことをお勧めします。
PW

11
私はそれに同意しますが、IMOの問題は、問題の領域に明確にマップされないことです。つまり、各条件が特定のシナリオにどのようにマップされるかによって、変更/拡張が困難になります。あるときに何が起きるEF4つの新しいシナリオが条件と?このifステートメントを正しく更新するにはどのくらい時間がかかりますか?コードレビューでは、問題がないかどうかをどのように確認しますか?問題は技術面ではなく、「ビジネス」面にあります。
Zdeslav Vojkovic

7
私はあなたが要因を取り除くことができると思いますA:(ABC + AB'C'D' = A(BC + B'C'D')これはA(B ^ C)'(C + D')私がこれを「単純化」と呼ぶことに注意するでしょうが、要因にさえすることができます)。
Maciej Piechotka 2018

28
@PWそのコメントは、コードと同じくらい理解しやすいように思われるため、少し無意味です。より良いコメントは、あなたが実際にその方程式を思いついた方法を説明するでしょう、すなわち、ステートメントはTTTT、TTTFおよびTFFFのためにトリガーするべきです。その時点で、代わりにコードにこれら3つの条件を記述するだけでよく、説明はまったく必要ありません。
Bernhard Barker

58

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

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

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

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

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
最も保守しやすいわけではありませんが、if条件を確実に単純化します。したがって、ビット単位の操作に関するいくつかのコメントを残すことは、ここではimoに絶対に必要です。
Adam Zahran

6
IMO、テーブルは、シナリオやフラグを追加することで拡張性が向上するため、最適なアプローチです。
Zdeslav Vojkovic

私はあなたの最初の解決策が好きです。読みやすく、修正が可能です。使用ブール値の明示、例えばでscenarioXに割り当てた値:1:私は2改善するだろうSCENARIO_2 = true << 3 | true << 2 | true << 1 | false;2:回避のSCENARIO_X変数、その後で利用可能なすべてのシナリオを保存します<std::set<int>。シナリオを追加することはmySet.insert( true << 3 | false << 2 | true << 1 | false;、たった3つのシナリオでは少しやり過ぎかもしれないようなものになるでしょう。OPは、私の回答で提案した迅速で、汚く、簡単なソリューションを受け入れました。
Gian Paolo

4
C ++ 14以降を使用している場合は、最初の解決策としてバイナリリテラルを使用することをお勧めします。0b1111、0b1110、0b1000の方がはるかに明確です。標準ライブラリ(std::find?)を使用して、これを少し単純化することもできます。
Bernhard Barker、

2
ここでのバイナリリテラルは、最初のコードをクリーンにするための最小要件であることがわかりました。現在の形式では、完全に暗号化されています。説明的な識別子が役立つかもしれませんが、それについてはよくわかりません。実際、scenario値を生成するためのビット操作は、不必要にエラーが発生しやすくなるように感じます。
Konrad Rudolph、

27

私の以前の答えはすでに受け入れられた答えです。ここでは、読みやすく、簡単で、この場合は将来の変更に対応できると思うものを追加します。

@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の場合、以前の「ifsの束」の回答は、エレガントでも保守可能でなくても、OP要件によりよく適合します。


いつでも以前の回答を編集して改善することができます。
Andreas

20

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

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

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

2
メインの関数の名前を除いて、私はこのアプローチが好きです。「ブールから… 何に?」—なぜ明示的に、、bitmap_from_boolsまたはbools_to_bitmap
Konrad Rudolph

はい@KonradRudolph、多分を除いて、私はもっと良い名前を考えることができませんbools_to_unsignedでした。ビットマップは良いキーワードです。編集。
Stack Danny

あなたが欲しいと思うsummary!= 0b1111u &&...a != b || a != c次の場合は常にtrueですb != c
MooseBoys 2018

17

簡略版は次のとおりです。

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

もちろん、この解決策は元の解決策よりも難読化されていることに注意してください。その意味は理解しにくいかもしれません。


更新:コメント内のMSalterはさらに単純な式を見つけました:

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

1
はい、分かりません。しかし、提案をありがとう。
Andrew Truckle 2018

コンパイラーの式を簡略化する機能を、参考としての簡略化と比較しました:コンパイラーエクスプローラー。gccは最適なバージョンを見つけられませんでしたが、その解決策は依然として良好です。ClangとMSVCはブール式の単純化を実行しないようです。
Oliv 2018

1
@AndrewTruckle:より読みやすいバージョンが必要な場合は、そのように言ってください。「簡略化された」と言いましたが、元のバージョンよりもさらに詳細なバージョンを受け入れます。
geza

1
simple確かにあいまいな用語です。多くの人々は、このコンテキストでは、コンパイラーがコードを生成するのではなく、開発者が理解するのがより簡単であることを理解しているため、より詳細な方が確かに簡単である可能性があります。
Zdeslav Vojkovic

1
@IsmaelMiguel:論理式が項の数に対して最適化されると、通常、元の意味が失われます。しかし、コメントを付けることができるので、それが何をするかは明らかです。たとえ受け入れられた回答であっても、コメントは害にはなりません。
geza 2018

12

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

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

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


最初のバージョンは非常に読みやすく、保守が容易なので、私は本当に気に入っています。2つ目は、少なくとも私にとっては読みにくく、おそらく平均以上のC ++スキルレベルが必要です。誰もが書けるものではありません。何か新しいことを学んだばかりです、ありがとう
ジャンパオロ

11

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

最後に、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つのブール値は実際には DWORDには4つの値がエンコードされているされます。

再びありがとうございました。


1
ソリューションを共有していただきありがとうございます。:)条件が地獄なら、それは実際には複合体よりも優れています。多分あなたはまだINCLUDE_ITEM1より良い方法で等に名前を付けることができ、あなたはすべて良いです。:)
Hardik Modha 2018

1
@HardikModhaええと、技術的には「生徒のアイテム」であり、フラグはそれらが「含まれる」かどうかを示すことです。そのため、この名前は、一般的に聞こえますが、この文脈では実際に意味があると思います。:)
Andrew Truckle 2018

11

OPのソリューションはそれを正確に行いますが、シナリオに名前を付けるための回答はありません。

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

関数の外でこれらのシナリオを再利用する予定がある場合(または必要に応じて)、評価する内容を示す関数を作成します(constexpr/ noexceptオプションですが推奨)。

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以上で最適ではないコードを生成したためにこれがボトルネックになっている場合は、コードを書き直してみてください。


私はこれがGian Paoloの(すでに素晴らしい)ソリューションよりも少し好きです:制御フローと上書きされる変数の使用を避けます-より機能的なスタイルです。
Dirk Herrmann、2018

9

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

このアプローチは、有効な条件の数が増えるかのようにスケーラブルであり、シナリオリストに条件を追加するだけです。


しかし、これは間違っていると確信しています。コンパイラは、の単一のバイナリ表現のみを使用することを前提としていtrueます。「ゼロ以外のものはすべて真」を使用するコンパイラは、このコードを失敗させます。に変換するtrue必要があることに注意してください。そのように保存する必要はありません。1
MSalters 2018

@ MSalters、tnx、私はあなたの要点を理解し、私はそれを知っています、少しのよう2 is not equal to true but evaluates to trueに、int 1 = trueすべてのtrueが同じint値に変換される限り、私のコードは強制されず、機能します。基礎となるintに忠実に、さらに詳しく説明していただけますか?
hessam hedieh 2018

memcmpブール条件をテストするためにa を実行することはC ++の方法ではなく、むしろ、それが確立されたCの方法であることを疑っています。
Konrad Rudolph

@hessamhedieh:ロジックの問題は「trueを基になるintに変換すること」です。それはコンパイラが機能する方法ではありません
MSalters 2018

コードにより、O(1)からO(n)に複雑さが増します。どの言語でも使用できる方法ではありません-C / C ++はそのままにしておきます。
mabel 2018

9

最初の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で提示されているより簡単な方法を使用する必要があります。

それ以外の場合、これらの条件が十分に確立されており、変更されない「堅固なルール」の一種である場合は、最後のコードスニペットを検討してください。


7

mchで提案されているように、次のことができます。

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

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

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


7

@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

すべての答えは非常に複雑で読みにくいです。これに対する最善の解決策は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することで、さらに読みやすくすることができます。


古いCプログラマーなので、「PackBools」マクロを定義し、それを「switch(PackBools(a、b、c、d))」とケースの両方に使用します。たとえば、直接「ケースPackBools(true 、true ...)」またはローカル定数として定義します。例:「const unsigned int scenario1 = PackBools(true、true ...);」
Simon F

6

わかりやすくするためにショートカット変数も使用します。前述のように、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 *変数に)意味のある名前を付けると、可読性と保守性が大幅に向上します。


6

特定の「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;
}

5
ある時点で、冗長性は読みやすさに悪影響を与え始めます。これは行き過ぎだと思います。
JollyJoker 2018

2
@JollyJoker私は実際にこの特定の状況に同意します-ただし、OPがすべてを非常に総称的に命名した方法からの私の直感は、「実際の」コードはおそらく彼らが示した例よりもはるかに複雑であるということです。本当に、私はこの代替案をそこに置きたかっただけです。それは、はるかに複雑で複雑なもののためにそれを構造化する方法だからです。しかし、あなたは正しいです-OPの特定の例では、それは過度に冗長であり、問​​題を悪化させます。

5

それは彼らが何を表すかに依存します。

たとえば場合1が鍵であり、そして23は、(彼らは上で同意する場合を除き、同意しなければならない2人ですNOT、彼らは三人必要- 4 -確認するために)最も読みやすいのかもしれません。

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

人気のリクエストによる:

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

2
あなたは正しいかもしれませんが、あなたのポイントを説明するために数字を使うことはあなたの答えを損ないます。わかりやすい名前を使用してください。
jxh 2018

1
@jxhこれらはOPが使用する数値です。取り外したところbValueです。
ispiro 2018

@jxh良くなったと思います。
ispiro 2018

4

ビット演算を行うことは、非常にクリーンで理解しやすいように見えます。

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

1
ビット単位の比較は私には読みやすく見えます。一方、構成は人工的に見えます。
xtofl

3

私は明確にするために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
If (!bValue1 || (bValue2 != bValue3) || (!bValue4 && bValue2))
{
// you have a problem
}
  • b1は常に真でなければならない
  • b2は常にb3と等しくなければなりません
  • b2(およびb3)がtrueの場合、b4をfalseにすることはできません

簡単な


3

受け入れられた答えよりも個人的な好みですが、私は書きます:

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

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


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

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++ライブラリの例があるかどうかはわかりません)


実際には、4つのブール値ではなく、4つの埋め込みブール値を持つDWORDを使用しています。今すぐ変更するには遅すぎます。しかし、あなたの提案をありがとう。
Andrew Truckle 2018

2

ネストされた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;
}

個人的には、可能であればステートメントのネストは通常​​避けます。このケースは見やすく読みやすいですが、新しい可能性が追加されると、ネストが非常に読みにくくなる可能性があります。しかし、シナリオが決して変わらない場合、それは間違いなく優れた読みやすいソリューションです。
Dnomyar96

@ Dnomyar96同意します。個人的にはネストされたifsも避けます。ロジックが複雑な場合は、ロジックを細かく分解することでロジックを理解しやすくなります。たとえば、bValue1blockに入ると、その中のすべてを精神プロセスの新しい新鮮なページとして扱うことができます。この問題への取り組み方は個人的なものかもしれないし、文化的なものかもしれません。
sardok

1

この質問にはいくつかの正解が出されていますが、私は別の見方をします。コードが複雑すぎるように見える場合、何かが正しくありません。コードはデバッグが難しく、「1回限りの使用」になる可能性が高くなります。

実生活では、次のような状況が見つかった場合:

         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

}

ジャンパオロの答えと同じように、短くて明確で、簡単に検証/デバッグできます。この場合、ブール式の詳細をコンパイラーに委任しています。


1

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

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

         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の回答よりも明らかに多くのコードですが、状況によっては、はるかに保守しやすくなる可能性があります。

  • 追加のブール値のプロパティまたはシナリオが追加された場合に変更する関数の中央セットがあります。
    • プロパティを追加するには、関数を1つだけ追加する必要があります。
    • シナリオを追加するenum場合、switchステートメントの未処理のケースに関するコンパイラ警告を有効にすると、そのシナリオを処理しないプロパティゲッターがキャッチされます。
  • ブール値のプロパティを動的に変更する必要がある場合は、すべての場所でそれらの組み合わせを再検証する必要はありません。個々のブールフラグを切り替える(フラグの組み合わせが無効になる可能性があります)代わりに、1つのシナリオから別のシナリオに遷移するステートマシンを使用します。

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


0

私の2セント:変数の合計(整数)を宣言して、

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

あなたが望む条件に対して合計をチェックしてください、それだけです。このようにして、将来さらに簡単に条件を追加して、読みやすくすることができます。


0

受け入れられた答えは、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)検証がそのケースと一致するかどうかを判別し、(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))

繰り返しになりますが、特定の例ではこのやり過ぎが見つかるかもしれませんが、満たす必要のあるはるかに複雑なケースのセットになってしまうと、コードがよりクリーンになります。


-2

簡単なアプローチは、許容できると思う答えを見つけることです。

はい=(boolean1 && boolean2 && boolean3 && boolean4)+ + ...

次に、可能であればブール代数を使用して方程式を単純化します。

この場合のように、acceptable1と2を組み合わせて (boolean1 && boolean2 && boolean3)ます。

したがって、最終的な答えは次のとおりです。

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

-3

ビットフィールドを使用:

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

それはCPPersにとって大きな残念です。しかし、UBは私の心配ではありません。http://coliru.stacked-crooked.com/a/2b556abfc28574a1で確認してください


2
これにより、非アクティブな共用体フィールドへのアクセスが原因でUBが発生します。
HolyBlackCat

正式にはC ++のUBです。共用体の1つのメンバーを設定して別のメンバーから読み取ることはできません。技術的には、整数値のビットに対してテンプレート化されたゲッター\セッターを実装する方が良いかもしれません。
スウィフト-金曜日のパイ

ユニオンのアドレスをに変換すると、動作は実装定義に移行するunsigned char*と思いますが、単純にのようなものを使用する((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1方がおそらくより効率的だと思います。
スーパーキャット2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.