8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

c++,自定义对象的排序:比较函数的要求

Daryan 1月前

6 0

我有一个自定义对象 std::vector 的指针向量。对象有一个索引,一个编号和一个时间戳(对象创建的时间)。时间戳是唯一的,编号是唯一的。

我有一个自定义对象的指针向量 std::vector<MyObject*> 。该对象具有索引、编号和时间戳(对象创建时间)。时间戳是唯一的,编号可以是 -1(尚未为该对象分配编号)或正值;如果对象的编号大于 0,则编号是唯一的。

class MyObject {
private:
    int id;
    int number;
    time_t timestamp;
public:
    MyObject(int id, int number, time_t timestamp) : id(id), number(number), timestamp(timestamp) {}
};

我想使用自定义比较函数对向量进行排序:如果我的对象的两个实例有一个数字,我使用数字(降序)排序,如果没有,我使用时间戳(降序)排序。

因此我在课程中添加了以下内容 MyObject

    static bool compareByDescendingNumberAndTimestamp(MyObject * a, MyObject * b) {
        if (a->number > 0 && b->number > 0) {
            return a->number > b->number;
        }
        return a->timestamp > b->timestamp;
    }

最后对向量进行排序:

std::vector<MyObject*> myObjects;
auto object1 = new MyObject(1, 24097, 1200);
auto object2 = new MyObject(2, 24096, 1100);
auto object3 = new MyObject(3, -1, 1000);
auto object4 = new MyObject(4, -1, 900);
auto object5 = new MyObject(5, 24099, 800);
auto object6 = new MyObject(6, 24095, 850);
myObjects.push_back(object1);
myObjects.push_back(object2);
myObjects.push_back(object3);
myObjects.push_back(object4);
myObjects.push_back(object5);
myObjects.push_back(object6);
std::sort(myObjects.begin(), myObjects.end(), MyObject::compareByDescendingNumberAndTimestamp);

我想要的顺序如下:

ID   Number  Timestamp
5    24099    800
1    24097   1200
2    24096   1100
3    -       1000
4    -        900
6    24095    850

但我实际得到的是:

ID   Number  Timestamp
1    24097   1200
2    24096   1100
3    -       1000
4    -        900
5    24099    800
6    24095    850

经过一番研究,我找到了 这个页面 。据我所知,我的比较函数不满足 Compare 。特别是 comp(a, b) 没有建立 严格的弱排序 关系。

有没有什么方法可以编写一个比较函数来按照我想要的顺序排列向量?

注意:我一直在使用 c++17。

编辑:

最小可重现示例(请注意,向量的初始顺序会影响最终结果):

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

class MyObject {
public:
    int id;
    int number;
    time_t timestamp;

    MyObject(int id, int number, time_t timestamp) : id(id), number(number), timestamp(timestamp) {}

    static bool compareByDescendingNumberAndTimestamp(MyObject * a, MyObject * b) {
        if (a->number > 0 && b->number > 0) {
            return a->number > b->number;
        }
        return a->timestamp > b->timestamp;
    }
};

int main() {
    std::vector<MyObject*> myObjects;
    auto object1 = new MyObject(1, 24097, 1200);
    auto object2 = new MyObject(2, 24096, 1100);
    auto object3 = new MyObject(3, -1, 1000);
    auto object4 = new MyObject(4, -1, 900);
    auto object5 = new MyObject(5, 24099, 800);
    auto object6 = new MyObject(6, 24095, 850);
    myObjects.push_back(object6);
    myObjects.push_back(object5);
    myObjects.push_back(object4);
    myObjects.push_back(object3);
    myObjects.push_back(object2);
    myObjects.push_back(object1);
    std::sort(myObjects.begin(), myObjects.end(), MyObject::compareByDescendingNumberAndTimestamp);

    std::cout << "ID\tNumber\tTimestamp" << std::endl;
    for (auto const & object: myObjects) {
        std::cout << std::to_string(object->id) << "\t" << std::to_string(object->number) << "\t"
        << std::to_string(object->timestamp) << std::endl;
    }

    return 0;
}

编辑 2我必须补充一点,实际上,没有用户真正关心排序:我发布了我在公司使用的应用程序的几个版本(我的问题是真实业务案例的简化版本),没有人抱怨。我只是试图以最优雅的方式解决“难题”。

帖子版权声明 1、本帖标题:c++,自定义对象的排序:比较函数的要求
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Daryan在本站《sorting》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 不清楚您要按照哪个编号对象对每个未编号对象进行排序。在更大的测试用例中,您可能会发现您的标准不明确。这种模糊性可能是比较器不符合要求的原因。

  • 让我们只看两个元素:如果你比较 (24099,800) 和 (-1,1000),由于第二个元素没有数字,所以第二个元素的时间戳更高。因此它会被排序得更高,这就是你得到的结果。如果你看看你想要实现的结果,你能描述一下为什么你期望这两个元素的排序不同吗?看起来你需要重新考虑标准

  • 谢谢大家。实际上,我似乎必须分两次排序:首先按编号排序,然后按时间戳推送未编号的项目。至少我的思维是这么认为的。我想知道是否有办法使用比较函数来表达这一点?

  • 开始之前:

    从你的问题来看:

    如果我的对象的两个实例有数字,我会使用数字(降序)排序,如果没有,我会使用时间戳(降序)排序。

    从你的问题下的评论来看,人们似乎不确定你句子的第二部分是否适用于被比较的两个对象都没有数字的情况,而没有明确的规则来比较有数字的对象和没有数字的对象。
    就我个人而言,我将其解释为: if either of the 2 objects I am comparing have no number, then they are to be ordered by descending timestamp .
    这与您呈现的想要应用的顺序一致(通过比较时间戳,ID 为 3 和 4 的对象按 2 到 6 之间的顺序排列,并忽略其中只有 2 个有数字的事实)。


    要回答标题中的问题(比较函数的要求),请参阅 std::sort 比较要求的 Compare requirements ,即您必须提供 严格的弱排序 函数。

    您在问题中描述的排序功能的问题在于,它实际上 不是 一个排序功能。

    快速回顾一下 序理论 ,一个序关系(注意)必须验证3个属性:

    • 自反性:a ≤ a
    • 反对称性:若 a ≤ b 且 b ≤ a 则 a = b
    • 传递性:若 a ≤ b 且 b ≤ c 则 a ≤ c

    当您具有这样的关系时,只需比较一对项目,即可保证获得一个排序集,并且分而治之的排序算法很大程度上依赖于这些属性才有效。
    如果你还没有,你很可能会陷入矛盾。

    让我们取 3 个项目,ID 2、3 和 5(显示为 (id number timestamp) ),看看你的函数说了什么:

    1. (5 24099 800) (2 24096 1100) (通过比较它们的数量)
    2. (2 24096 1100) (3 - 1000) (通过比较它们的时间戳)
    3. (3 - 1000) (5 24099 800) (通过比较它们的时间戳)
    4. (2 24096 1100) (5 24099 800) (根据传递性 2. 和 3.)
    5. (2 24096 1100) = (5 24099 800) (利用1.和4.的反对称性)

    这就是矛盾所在。你的程序不会检测到它,因为它相信你为它提供了正确的顺序关系。

    结论:您必须改变您的函数以使其成为适当的顺序关系(实际上,C++ 需要严格的顺序关系,因此 a < a 必须为假)。


    既然理论已经讲清楚了,让我们看看如何对 myObjects 向量进行排序。

    一个有效的顺序是:

    • 所有带有 的对象 number == -1 (实际上是所有带有 的对象 number < 0 )都位于末尾,并按降序排列 timestamp .
    • 所有对象 number >= 0 按降序排列 number .

    这可以简化为按数字、时间戳降序排序。

    为了安全起见,我会测试是否 nullptr 在任何地方遇到(你没有这样做但 实际上 应该一直这样做)并将它们推到最后(我让你改变循环来打印对象)。

    std::sort(myObjects.begin(), myObjects.end(), 
        [] (auto const a, auto const b) -> bool {
        if (!a)
            return false;
        if (!b)
            return true;
        return std::make_pair(a->number, a->timestamp) > std::make_pair(b->number, b->timestamp);
    });
    

    另一个有效的顺序是:

    • 所有带有 的对象 number < 0 都位于开头,并按降序排列 timestamp .
    • 所有对象 number >= 0 按降序排列 number .

    在这种情况下,它不能像上面那样简化,你会得到:

    std::sort(myObjects.begin(), myObjects.end(), 
        [] (auto const a, auto const b) -> bool {
        if (!a)
            return false;
        if (!b)
            return true;
        if (a->number >= 0 && b->number >= 0)
            return a->number > b->number;
        if (a->number < 0 && b->number < 0)
            return a->timestamp > b->timestamp;
        return (a->number < 0); // we know a->number and b->number are not the same sign
    });
    

    作为一种更灵活的替代方法,您可以分几个步骤进行排序,使用 std::partition 将向量划分为可比较的子部分(这比 更有效 std::sort )。
    lambda 表达式将更容易编写:

    auto nonNullEnd = std::partition(myObjects.begin(), myObjects.end(),
        [](auto const o) -> bool {
        return static_cast<bool>(o);
    });
    // nonNullEnd now points to the first nullptr,
    // therefore it is safe not to test pointers before it.
    auto negNumberedObjectEnd = std::partition(myObjects.begin(), nonNullEnd,
        [](auto const o) -> bool {
        return o->number < 0;
    });
    // negNumberedObjectEnd now points to the first object with number >= 0.
    // We can use it as the past-the-end iterator for objects with number < 0
    // and as the begin iterator for objects with number >= 0.
    
    std::sort(myObjects.begin(), negNumberedObjectEnd,
        [] (auto const a, auto const b) -> bool {
            return a->timestamp > b->timestamp;
    });
    std::sort(negNumberedObjectEnd, nonNullEnd, 
        [] (auto const a, auto const b) -> bool {
        return a->number > b->number;
    });
    

    当然还有其他方法来解决问题,如果您想尝试的话,我推荐最后一种方法。

  • 顺便说一句,我不同意“始终检查 nullptr”。我要么允许 nullptr 出现,要么不允许。在第二种情况下,我可能只是主张这一要求,但仍然无法正确处理它们,因为我认为完全没有理由浪费我的时间来支持编程错误,而且通常没有很好的方法来处理它们,因为设计根本没有考虑它们。另一方面,我通常不会做的是将原始指针放入容器中——这种情况可能发生,但很少见。

  • 好吧,问题的主题不是原始指针与智能指针,所以我不打算讨论这一点。至于“始终检查 nullptr”,我的目的是强调单个排序代码段的复杂 lambda 如何在分区代码段中转变(我在发布我的答案之前做了一些向上滚动/修改,我写的第一个版本没有这一点)。

  • 现在,你完全看不到支持编程错误的理由,这对我来说是一个非常强烈的声明;如果你像我以前一样,在一个开发实践不佳的团队里工作过,你会竭尽全力(这确实是很多工作,我可以告诉你)来防止白痴把事情搞砸,通常是在 1-2 年后...至少我在我的第一份工作中了解到,事情可以变得多么糟糕是没有限制的 :)

  • Zoup 1月前 0 只看Ta
    引用 9

    @Atmo 可以只记录“在出现 nullptr 时终止程序”,那么传递 nullptrs 就不是编程错误。

返回
作者最近主题: