【问题标题】:Crash in std::sort - sorting without strict weak orderingstd::sort 中的崩溃 - 没有严格弱排序的排序
【发布时间】:2018-08-04 22:40:14
【问题描述】:

我正在尝试对项目向量进行排序。如代码cmets中所述,排序应为:

行动点数较多的参与者 (mAp) 先行。当出现平局时,与战斗发起者(mBattleInitiator)具有相同性格(mDisposition)的参与者先行。

以下代码(简化示例)在 macOS 上崩溃,可能是由于我的排序实现不正确:

#include <QtCore>

class AiComponent
{
public:
    enum Disposition {
        Friendly,
        Hostile
    };

    AiComponent(Disposition disposition) : mDisposition(disposition) {}
    ~AiComponent() { qDebug() << "Destroying AiComponent"; }

    Disposition mDisposition;
};

class BattleManager
{
public:
    BattleManager() : mBattleInitiator(AiComponent::Hostile) {}

    class Turn {
    public:
        Turn() : mAp(1) {}

        Turn(QSharedPointer<AiComponent> aiComponent) :
            mAiComponent(aiComponent),
            mAp(1)
        {
        }

        Turn(const Turn &rhs) :
            mAiComponent(rhs.mAiComponent),
            mAp(1)
        {
        }

        QSharedPointer<AiComponent> mAiComponent;
        int mAp;
    };

    void addToTurnQueue(QSet<QSharedPointer<AiComponent>> aiComponents);

    AiComponent::Disposition mBattleInitiator;
    QVector<Turn> mTurnQueue;
    Turn mActive;
};

void BattleManager::addToTurnQueue(QSet<QSharedPointer<AiComponent> > aiComponents)
{
    foreach (auto aiComponent, aiComponents) {
        mTurnQueue.append(Turn(aiComponent));
    }

    // Sort the participants so that ones with more action points (mAp) go first.
    // When there is a tie, participants with the same disposition (mDisposition)
    // as the initiator of the battle (mBattleInitiator) go first.
    std::sort(mTurnQueue.begin(), mTurnQueue.end(), [=](const Turn &a, const Turn &b) {
        if (a.mAp > b.mAp)
            return true;

        if (a.mAp < b.mAp)
            return false;

        // At this point, a.mAp is equal to b.mAp, so we must resolve the tie
        // based on mDisposition.
        if (a.mAiComponent->mDisposition == mBattleInitiator)
            return true;

        if (b.mAiComponent->mDisposition == mBattleInitiator)
            return false;

        return false;
    });
}

int main(int /*argc*/, char */*argv*/[])
{
    BattleManager battleManager;

    for (int i = 0; i < 20; ++i) {
        qDebug() << "iteration" << i;

        QSet<QSharedPointer<AiComponent>> participants;

        AiComponent::Disposition disposition = i % 2 == 0 ? AiComponent::Hostile : AiComponent::Friendly;
        QSharedPointer<AiComponent> ai(new AiComponent(disposition));
        participants.insert(ai);

        battleManager.addToTurnQueue(participants);
    }

    // This should print (1 1), (1 1), ... (1 0), (1 0)
    foreach (auto turn, battleManager.mTurnQueue) {
        qDebug() << "(" << turn.mAp << turn.mAiComponent->mDisposition << ")";
    }

    return 0;
}

我已经查看了有关该主题的其他答案。他们中的大多数人只是说“将其实现为 a > b”,这在我的情况下不起作用。有一些看起来相关但对我没有帮助:

实现我所追求的最简单的方法是什么?

【问题讨论】:

  • 你需要实现严格的弱排序。没有办法解决这个问题。如何做到这一点取决于您。
  • 据我了解,严格的弱排序意味着我无法解决关系。如果我的理解是正确的,那么这对我来说不是一个选择。如果我的理解不正确,请提供一个说明这一点的答案,以及一个解决平局的简单代码示例。
  • 如果 tie 解析器也遵循严格的弱排序,您可以解析 tie。参见例如对字典排序做了什么(例如按字母顺序排序字符串)
  • 您需要对 2 元组执行严格的弱排序。见stackoverflow.com/q/979759/72178
  • 正确解决关系意味着他们必须自己实施严格的弱排序。如果你有特殊元素应该放在非特殊元素之前,那么if (element a is special and element b is not special) return true; else return false;

标签: c++ sorting strict-weak-ordering


【解决方案1】:

尚未解释崩溃的原因。 std::sort 的大多数实现都是基于快速排序,特别是 Hoare 分区方案,它从左到右扫描数组,只要元素值 枢轴值。这些扫描依赖于这样一个事实,即找到元素值 = 枢轴值将停止扫描,因此没有检查超出数组边界的扫描。如果用户提供的小于比较函数在元素相等的情况下返回 true,则任何一次扫描都可能超出数组边界并导致崩溃。

在调试构建的情况下,可以对用户比较函数进行测试以确保比较小于且不小于或等于,但对于发布构建,目标是速度,因此这些检查是未执行。

【讨论】:

  • Here 创建自动工具以检测排序回调中的错误(仅支持qsort atm 但std::sort 相当多)。
【解决方案2】:

我会在你的代码中去掉注释并解释它有什么问题(如果有的话),以及你将如何解决它。

// Sort the participants so that ones with more action points (mAp) go first.

目前还不错

// When there is a tie, participants with the same disposition (mDisposition) as the initiator of the battle (mBattleInitiator) go first.

如果两个参与者都具有与发起者相同的倾向怎么办?即使您可以保证没有 2 个元素会满足此条件,排序算法也允许将一个元素与其自身进行比较。在这种情况下,此测试将返回 true,这违反了严格弱排序的条件之一,即元素必须与自身比较相等(即,compare(a,a) 必须始终为 false)。

也许你想说如果a 与发起者有相同的倾向,而b 没有,那么a 应该被认为小于b。这可以编码为:

return dispositionOfA == mBattleInitiator && dispsitionOfB != mBattleInitiator;

所以你的完整测试应该是这样的:

if (a.mAp > b.mAp)
    return true;
if (a.mAp < b.mAp)
    return false;

return a.mAiComponent->mDisposition == mBattleInitiator &&
       b.mAiComponent->mDisposition != mBattleInitiator;

【讨论】:

  • 谢谢!这是一个很好的答案,因为它准确地解释了我缺少什么以及如何解决它。您是否介意简要描述一下严格-弱排序的其他条件(或者只是提供一个指向该页面的链接)?
  • 对于qsort,可以使用自动违规检测器herestd::sort 等效是可行的,所以如果需要,请告诉我)。
猜你喜欢
  • 1970-01-01
  • 2016-02-04
  • 2018-04-17
  • 2013-02-14
  • 1970-01-01
  • 2010-11-20
  • 1970-01-01
  • 2013-05-25
  • 2019-06-09
相关资源
最近更新 更多