【问题标题】:C++ difference in behavior between if statement setting condition to true and or-ing a condition in loopC++ if 语句将条件设置为 true 和循环中的条件之间的行为差​​异
【发布时间】:2017-08-02 21:43:32
【问题描述】:

我在为我的 CS 课程开发 Datalog 解释器时遇到了一个奇怪的问题,即我的规则评估需要通过太多次才能完成。查看我的代码后,我在以下内容中进行了两项修改,修复了我的评估以正确的通过次数执行:

//original form
bool addedFacts = false;
for (X x: xs) {
    addedFacts = addedFacts || set.insert(x).second;
}
//modified form
bool addedFacts = false;
for (X x: xs) {
    if (set.insert(x).second) {
        addedFacts = true;
    }
}

在我看来,这两种代码结构在逻辑上是等价的。一个执行正确而一个执行不正确/效率低下是否有原因? 这是正在发生的问题的可构建示例:

#include <iostream>
#include <set>
#include <vector>

using std::set;
using std::vector;
using std::cout;
using std::endl;

const int CAP = 100;

class Rule {
public:
    int factor;
    Rule(int factor) {
        this->factor = factor;
    }
    bool evaluateInefficient(set<int>& facts) {
        vector<int> data;
        bool addedFacts = false;
        for (int fact : facts) {
            data.push_back(fact);
        }
        for (int datum : data) {
            int newFact = datum * factor;
            if (newFact < CAP) {
                addedFacts = addedFacts || facts.insert(newFact).second;
            }
        }
        return addedFacts;
    }
    bool evaluate(set<int>& facts) {
        vector<int> data;
        bool addedFacts = false;
        for (int fact : facts) {
            data.push_back(fact);
        }
        for (int datum : data) {
            int newFact = datum * factor;
            if (newFact < CAP) {
                if (facts.insert(newFact).second) {
                    addedFacts = true;
                }
            }
        }
        return addedFacts;
    }
};

int doublyInefficient(vector<Rule>& rules) {
    set<int> facts;
    facts.insert(1);
    bool addedFacts = true;
    int passes = 0;
    while (addedFacts) {
        passes++;
        addedFacts = false;
        for (Rule rule : rules) {
            addedFacts = addedFacts || rule.evaluateInefficient(facts);
        }
    }
    return passes;
}

int singlyInefficient(vector<Rule>& rules) {
    set<int> facts;
    facts.insert(1);
    bool addedFacts = true;
    int passes = 0;
    while (addedFacts) {
        passes++;
        addedFacts = false;
        for (Rule rule : rules) {
            addedFacts = addedFacts || rule.evaluate(facts);
        }
    }
    return passes;
}

int efficient(vector<Rule>& rules) {
    set<int> facts;
    facts.insert(1);
    bool addedFacts = true;
    int passes = 0;
    while (addedFacts) {
        passes++;
        addedFacts = false;
        for (Rule rule : rules) {
            if (rule.evaluate(facts)) {
                addedFacts = true;
            }
        }
    }
    return passes;
}

int main(int argc, char* argv[]) {
    //build the rules
    vector<Rule> rules;
    rules.push_back(Rule(2));
    rules.push_back(Rule(3));
    rules.push_back(Rule(5));
    rules.push_back(Rule(7));
    rules.push_back(Rule(11));
    rules.push_back(Rule(13));
    //Show three different codes that should (in my mind) take the same amount of passes over the rules but don't
    cout << "Facts populated after " << doublyInefficient(rules) << " passes through the Rules." << endl;
    cout << "Facts populated after " << singlyInefficient(rules) << " passes through the Rules." << endl;
    cout << "Facts populated after " << efficient(rules) << " passes through the Rules." << endl;
    getchar();
}

在 Visual Studio 2017 上以调试和发布模式(32 位)运行时,我得到以下输出。据我所知,代码未优化。

Facts populated after 61 passes through the Rules.
Facts populated after 17 passes through the Rules.
Facts populated after 7 passes through the Rules.

【问题讨论】:

  • 在第一种情况下,一旦set.insert(x).second 为真,循环就什么也不做。第二种情况,每次都会调用set.insert(x)

标签: c++ loops if-statement set logical-or


【解决方案1】:
addedFacts = addedFacts || set.insert(x).second;

if (set.insert(x).second) {
    addedFacts = true;
}

绝对不是一回事。第一段代码相当于:

if (!addedFacts) {
    addedFacts = set.insert(x).second;
}

!addedFacts 检查有很大的不同。

【讨论】:

    【解决方案2】:

    由于短路评估而产生差异: 考虑(expr1 || expr2) 形式的表达式。短路意味着如果expr1 计算为true,则表达式expr2 将根本不会被计算(参见this online c++ standard draft,重点是我的):

    5.15 逻辑或运算符

    ||运算符组从左到右。操作数都是 上下文转换为布尔(子句 [conv])。如果返回 true 它的任何一个操作数都为真,否则为假。与 |、|| 不同 保证从左到右的评估; 此外,第二个操作数是 如果第一个操作数的计算结果为真,则不计算

    因此,在您的表达式addedFacts || set.insert(x).second 中,从addedFacts 第一次变为true 的那一点,表达式set.insert(x).second 将不再执行。我想这是“错误”的行为,因为您的 set 将不包含相应的 xes。

    【讨论】:

    • 好的,现在我明白发生了什么。该程序应该在每次通过时评估每条规则,并在每条规则中添加每个新事实。短路评估阻止了我的代码这样做,从而大大增加了所需的通过次数。谢谢!
    猜你喜欢
    • 2022-06-21
    • 1970-01-01
    • 2020-09-30
    • 2011-09-12
    • 2016-09-07
    • 2021-09-18
    • 2023-04-04
    • 2013-06-01
    • 2022-11-26
    相关资源
    最近更新 更多