如何改进检查4个布尔值是否与某些情况匹配的逻辑


118

我有四个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 '18

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

14
实际情况是什么?如果您只是给东西起个适当的名字,通常情况会变得简单得多,例如bool scenario1 = bValue1 && bValue2 && bValue3 && bValue4;
idclev 463035818 18/12/3

6
使用有意义的名称,可以将每个复杂条件提取到方法中,然后在if条件中调用该方法。这将更具可读性和可维护性。例如,看一下链接中提供的示例。refactoring.guru/decompose-conditional
Hardik Modha '18

Answers:


195

我的目标是提高可读性:您只有3种情况,请用3种单独的ifs处理它们:

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个场景的情况下,我不会采用“如果前三个值是正确的,我可以避免检查第四个值”之类的东西:这将使您的代码难以阅读和维护。

不是一个优雅的解决方案 也许 当然可以,但是在这种情况下还可以:简单易读。

如果您的逻辑变得更加复杂,请丢弃该代码,并考虑使用更多代码来存储不同的可用方案(如Zladeck所建议)。

我真的很喜欢此答案中给出的第一个建议:易于阅读,不易出错,可维护

(几乎)关闭主题:

我在StackOverflow上没有写很多答案。有趣的是,上面接受的答案是我历史上迄今为止最受赞赏的答案(我认为从来没有超过5-10次投票),而实际上这并不是我通常认为的“正确”方法。

但是,简单性通常是“正确的做法”,许多人似乎都认为这一点,我应该比我更重视它:)


1
确保@hessamhedieh,仅在少数可用方案中可以。正如我说的,如果事情变得更复杂,则最好寻找其他东西
Gian Paolo

4
通过将所有条件堆叠到初始化器中valid并用分隔它们||,而不是valid在单独的语句块中进行更改,可以进一步简化此操作。我无法在示例中添加示例,但您可以将||操作符沿左侧垂直对齐以使其非常清楚。各个条件已经加上了所需的括号(for if),因此您无需在表达式中添加已经存在的字符以外的任何字符。
Leushenko '18

1
@Leushenko,我认为混合括号,&&和|| 条件是很容易出错的(另一个回答中的人说,OP中代码的括号中有一个错误,也许已经纠正了)。当然,正确对齐可以有所帮助。但是优势是什么?更具可读性?更容易维护?我不这么认为。当然只是我的意见。可以肯定的是,我真的很讨厌在代码中包含很多if。
吉安·保罗

3
我将其包装为a,if($bValue1)因为它始终必须是正确的,从技术上讲,它可以对性能进行一些细微的改进(尽管在这里谈论的金额可以忽略不计)。
Martijn

2
FWIW:只有两种情况:前两种是相同的情况且不依赖于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

我们可以使用卡诺图,并将您的方案简化为一个逻辑方程式。我已经使用带有电路的Online Karnaugh映射求解器来处理4个变量。

在此处输入图片说明

这样产生:

在此处输入图片说明

更改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方案的相关注释是一种好习惯。

96
尽管从技术上讲是正确的,但此代码需要大量注释才能在几个月后由另一位开发人员进行编辑。
Zdeslav Vojkovic

22
@ZdeslavVojkovic:我只想在方程式中添加一条注释。//!(ABC + AB'C'D') (By K-Map logic)。如果开发人员不了解K-Map,那将是一个学习K-Map的好时机。
PW

11
我同意这一点,但是IMO的问题是它不能清楚地映射到问题域,即每个条件如何映射到特定场景,这使得更改/扩展变得困难。当有发生什么EF条件以及4级新的场景?if正确更新此语句需要多长时间?代码审查如何检查是否正常?问题不在于技术方面,而在于“业务”方面。
Zdeslav Vojkovic

7
我认为您可以排除A:(尽管我会谨慎地称之为“简化”,ABC + AB'C'D' = A(BC + B'C'D')这也可以考虑在内A(B ^ C)'(C + D'))。
Maciej Piechotka,

28
@PW该注释似乎和代码一样可理解,因此毫无意义。更好的注释将解释您实际上是如何得出该方程式的,即该语句应触发TTTT,TTTF和TFFF。到那时,您最好只在代码中编写这三个条件,而不需要任何解释。
伯恩哈德·巴克

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绝对需要按位操作留下一些评论。
亚当·扎赫兰

6
IMO,表是最好的方法,因为它可以通过其他方案和标志来更好地扩展。
Zdeslav Vojkovic '18

我喜欢您的第一个解决方案,易于阅读且易于修改。我将进行2处改进:1:为场景SCENARIO_2 = true << 3 | true << 2 | true << 1 | false;X 分配值,并明确指示使用的布尔值;例如2:避免使用SCENARIO_X变量,然后将所有可用场景存储在<std::set<int>mySet.insert( true << 3 | false << 2 | true << 1 | false;对于3种情况,添加一种情况可能只是一个小小的矫over过正,OP接受了我在回答中建议的快速,肮脏和简单的解决方案。
吉安·保罗

4
如果您使用的是C ++ 14或更高版本,则建议第一个解决方案使用二进制文字-0b1111、0b1110和0b1000更清晰。您还可以使用标准库(std::find?)对此进行一些简化。
伯恩哈德·巴克

2
我发现这里的二进制文字是使第一个代码干净的最低要求。以目前的形式,它是完全神秘的。描述性标识符可能会有所帮助,但我什至不确定。实际上,产生scenario值的位操作使我感到不必要地容易出错。
康拉德·鲁道夫'18

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的要求,即使它既不优雅也不可维护。


您知道您可以随时编辑以前的答案并进行改进。
安德烈亚斯

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
除了主要功能的名称外,我喜欢这种方法:“从bool ...到什么?” —为什么不显式显示bitmap_from_boolsbools_to_bitmap
康拉德·鲁道夫

是的@KonradRudolph,我想不到一个更好的名字了,也许吧bools_to_unsigned。位图是一个很好的关键字。编辑。
Stack Danny

我想你要summary!= 0b1111u &&...a != b || a != c如果b != c
MooseBoys

17

这是一个简化的版本:

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

注意,当然,此解决方案比原始解决方案更难理解,其含义可能更难理解。


更新:MSalters在评论中发现了一个更简单的表达式:

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

1
是的,但很难理解。但是谢谢你的建议。
安德鲁·特拉

我将编译器简化表达式的能力与您的简化进行了比较,以作为参考:编译器浏览器。gcc找不到最佳版本,但其解决方案仍然不错。Clang和MSVC似乎不执行任何布尔表达式简化。
奥利夫,

1
@AndrewTruckle:请注意,如果您需要一个更具可读性的版本,请这样说。您已经说过“简体”,但是您接受的版本比原始版本更为冗长。
geza

1
simple确实是一个模糊的术语。在这种情况下,许多人将其理解为开发人员更容易理解,而不是编译器生成代码,因此,冗长的确可以简化。
Zdeslav Vojkovic,

1
@IsmaelMiguel:当逻辑公式针对术语数量进行优化时,通常会丢失原始含义。但是人们可以对此发表评论,因此很清楚它的作用。即使对于已接受的答案,发表评论也不会造成伤害。
geza

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

编译器可以内联代码,消除任何迭代并为您构建自己的逻辑。同时,您的代码准确反映了您对问题的理解程度。


第一个版本非常易于阅读且易于维护,我非常喜欢它。至少对于我来说,第二个更难阅读,并且它的c ++技能水平可能超过平均水平,肯定超过我的水平。不是每个人都能写的东西。刚刚学到了新知识,谢谢
Gian Paolo

11

我只是在这里提供我的答案,就像有人建议显示我的解决方案的评论一样。我要感谢大家的见解。

最后,我选择添加三个新的“方案” 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
}

在我的实时应用程序中,实际上从a DWORD中提取了4个bool值,而其中已编码了4个值。

再次感谢大家。


1
感谢您分享解决方案。:)如果条件恶劣,实际上比复杂的要好。也许您仍然可以INCLUDE_ITEM1用更好的方式命名等,但你们都很好。:)
Hardik Modha

1
@HardikModha好吧,从技术上讲,它们是“学生项目”,并且标记是用来指示是否要“包含”它们。因此,我认为这个名称虽然听起来很通用,但在这种情况下实际上是有意义的。:)
安德鲁·特拉克

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的(已经不错的)解决方案更喜欢它:它避免了控制流,并且避免使用被覆盖的变量-更具功能性的样式。
德克·赫尔曼

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

@MSalters,tnx,我明白你的意思,有点像2 is not equal to true but evaluates to trueint 1 = true只要将所有true都转换为相同的int值,我的代码就不会强制运行,所以这是我的问题:为什么编译器应在转换时随机执行忠实于底层int,请您详细说明一下?
hessam hedieh 18/12/3

执行a memcmp来测试布尔条件不是 C ++的方式,我相当怀疑这是否是既定的C方式。
康拉德·鲁道夫'18

@hessamhedieh:您逻辑中的问题是“将true转换为基础int”。那不是编译器的工作方式,
MSalters

您的代码将复杂度从O(1)增加到O(n)。没有任何一种语言可以使用-抛开C / C ++。
mabel

9

很容易注意到,前两种情况是相似的-它们共享大多数条件。如果您想选择当前的场景,则可以这样编写(这是修改后的@ 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))
)

第一行涵盖了前两个情况,第二行涵盖了最后一个。

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语句中使用常量和/或它们,以提高可读性。


作为老的C程序员,我将定义一个“ PackBools”宏,并将其用于“ switch(PackBools(a,b,c,d))”和案例中,例如直接使用“ case PackBools(true ,true ...)”或将其定义为局部常量。例如“ const unsigned intcensing1 = PackBools(true,true ...);”
西蒙F

6

为了清楚起见,我还将使用快捷方式变量。如前所述,方案1等于方案2,因为bValue4的值不影响这两个方案的真相。

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

关注问题的可读性,而不是特定的“ 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

2
@JollyJoker在这种特定情况下,我确实同意-但是,从OP极其通用地命名所有内容的方式来看,我的直觉是他们的“真实”代码可能比给出的示例复杂得多。真的,我只是想把这个替代方案放在那里,因为这是我为更复杂/更复杂的事物构建它的方式。但是您是对的-对于OP的特定示例,它过于冗长,使情况更糟。

5

这取决于它们代表什么。

例如,如果1是键,而23是必须同意的两个人(除非他们同意,NOT他们需要第三人-4-来确认),否则可读性最高的可能是:

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

受欢迎的要求:

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

2
您可能是对的,但使用数字来说明您的观点会损害您的答案。尝试使用描述性名称。
jxh

1
@jxh这些是OP使用的数字。我只是删除了bValue
ispiro '18

@jxh我希望现在会更好。
ispiro

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)为真,则b4不能为假

简单


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 ++库示例)


实际上,我不是使用四个布尔值,而是一个带有四个嵌入式BOOLS的DWORD。现在更改它为时已晚。但是,谢谢您的建议。
安德鲁·特拉

2

if对于某些人来说,嵌套s可能更容易阅读。这是我的版本

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

就个人而言,如果可能的话,我通常会避免嵌套if语句。尽管这种情况很好看并且很容易理解,但是一旦添加了新的可能性,嵌套就会变得很难阅读。但是,如果情况永远不会改变,那么它绝对是一个不错的可读解决方案。
Dnomyar96 '18

@ Dnomyar96我同意。我个人也避免嵌套ifs。有时候,如果逻辑很复杂,我可以通过将逻辑分解成碎片来理解它。例如,一旦您输入了bValue1块,您就可以将其中的所有内容视为您的思维过程中的新页面。我敢打赌,解决问题的方式可能是非常个人的甚至是文化的东西。
sardok

1

对于这个问题,已经给出了几个正确的答案,但是我会采取不同的看法:如果代码看起来太复杂,那么事情就不太正确了。该代码将难以调试,并且更有可能是“只能使用一次”。

在现实生活中,当我们发现这样的情况时:

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

当四个状态以这种精确的模式连接时,我们正在处理模型中某些“实体”的配置

一个极端的隐喻是,如果我们不了解模型中的“人类”,如果我们不知道它们是作为整体实体存在的,其组成部分与特定的自由度相关联:我们将不得不描述“躯干”的独立状态, “手臂”,“腿部”和“头部”,使所描述的系统变得复杂。立即产生的结果是不自然地复杂的布尔表达式。

显然,降低复杂性的方法是抽象,而c ++中的一种选择工具是对象范式

所以问题是:为什么会有这种模式?这是什么,代表什么?

由于我们不知道答案,因此我们可以依靠数学上的抽象:数组:我们有三个场景,每个场景现在都是一个数组。

                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

如果您摆脱了布尔标志,则不必担心布尔标志的无效组合。

可接受的值为:

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

您显然具有三种状态(方案)。最好对此建模,并从这些状态派生布尔属性,而不是相反。

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的答案中的代码更多,但是根据您的情况,这可能更易于维护:

  • 如果添加了其他布尔属性或方案,则有一组主要的函数可以修改。
    • 添加属性仅需要添加一个功能。
    • 如果添加方案,则enumswitch语句中启用有关未处理案例的编译器警告将捕获不处理该方案的属性获取器。
  • 如果需要动态修改布尔属性,则无需在所有地方重新验证其组合。而不是切换单个布尔标志(这可能导致标志的无效组合),而是使用一种状态机,该状态机从一种情况转换为另一种情况。

这种方法还具有非常高效的副作用。


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

同样,对于您的特定示例,您可能会发现这种矫kill过正的做法,但是如果最终遇到了需要解决的更为复杂的情况,它会创建更简洁的代码。


-2

一种简单的方法是找到您认为可接受的答案。

是=(布尔值1 &&布尔值2 &&布尔值3 &&布尔值4)+ + ...

现在,如果可能的话,使用布尔代数简化方程。

像在这种情况下,accept1和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,您不能设置一个union成员并从另一个成员读取。从技术上讲,最好对整数值的位实现模板化的getter \ setter方法。
斯威夫特-星期五派

我认为,如果要将工会的地址转换为,行为将转变为“实施定义” unsigned char*,尽管我认为仅使用类似((((flag4 <<1) | flag3) << 1) | flag2) << 1) | flag1方法可能会更有效。
超级猫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.