C ++ 11按几个属性对自定义对象进行排序

时间:2015-05-19 07:14:26

标签: c++ c++11

有一组自定义结构元素:

struct MyStruct
{
    int id;
    std::string currencyCode;
    int month;
    int year;
    int amount;
};

此数据将显示在某个表中,该表允许按多列排序(通过单击按住Ctrl键的表列)。

按一个属性对客户对象集合进行排序非常简单:

vector<MyStruct> values;

std::sort( values.begin( ), values.end( ), [ ]( const MyStruct& lhs, const MyStruct& rhs )
{
   return lhs.key < rhs.key;
});

struct MyStruct_x_Greater
{
    bool operator()( const MyStruct& lMyStruct, const MyStruct& rMyStruct ) const {
        return lMyStruct.x < rMyStruct.x;
    }
};

std::sort( values.begin(), values.end(), MyStruct_x_Greater() );

但是如何通过几个属性进行排序(类似于sql ORDER BY column1 DESC, column2 ASC)?

6 个答案:

答案 0 :(得分:34)

只需更换比较器即可。假设您想按年份,然后按月,然后按金额订购,那么:

std::sort( values.begin(), values.end(), [ ]( const MyStruct& lhs, const MyStruct& rhs )
{
   return std::tie(lhs.year, lhs.month, lhs.amount) <
          std::tie(rhs.year, rhs.month, rhs.amount);
});

std::tuple的{​​{1}}进行字典比较,因此不需要自己动手(并且冒着错误的风险)。要对属性执行降序排序,只需为该属性交换operator<lhs

答案 1 :(得分:14)

您需要选择 在运行时 的排序顺序,这与大多数类似的问题(以及您收到的一些下意识的答案)不同。我建议你给每个字段一个id号,并添加一个函数来比较id:

指定的字段
template <typename T>
int cmp(const T& lhs, const T& rhs)
{
    return lhs < rhs ? -1 : lhs == rhs ? 0 : 1;
}

struct MyStruct
{
    int id;   // 0
    std::string currencyCode;  // 1
    int month;  // 2
    int year;  // 3
    int amount;  // 4

    int cmp(int field_id, const MyStruct& rhs) const
    {
        switch (field_id)
        {
          case 0: return cmp(id, rhs.id);
          case 1: return cmp(currencyCode, rhs.currencyCode);
          case 2: return cmp(month, rhs.month);
          case 3: return cmp(year, rhs.year);
          case 4: return cmp(amount, rhs.amount);
          default: throw ...cases out of sync with code...;
        }
    }
};

// update this as your column heading are clicked...
std::vector<int> sort_field_ids = ...;

std::sort(begin(values), end(values),
    [&](const MyStruct& lhs, const MyStruct& rhs)
    {
        for (auto fid : sort_field_ids)
        {
            int cmp = lhs.cmp(fid, rhs);
            if (cmp) return cmp == -1;
        }
        // fall back on something stable and unique
        return lhs.id < rhs.id;
    });

要支持降序排序,请维护一个额外的标记(例如std::vector<std::pair<int, bool>> sort_field_ids),然后return (cmp == -1) ^ descending;(从fid,descending中提取pair,或将其称为{{ 1}} / .first如果您愿意的话。)

更好的是,找一个图形库,它有一个像样的网格/表格小部件,可以在内部进行排序。

答案 2 :(得分:9)

通过为字段分配ID来解决这个问题有几个缺点:

  • 可能不包括案例(如throw所示)
  • 按相反顺序排序需要额外标记
  • 代码中哪个ID代表哪个字段
  • 并不明显
  • 它不易扩展(您总是需要更新您的ID映射)

或者,您可以定义&#34;比较器&#34;,返回-101,具体取决于第一个参数是否小于,等于或分别大于第二个。然后可以非常一般地组合和反转这些比较器,并将其用作排序谓词。

给出结构字段的一组这些比较器,你可以这样编写它们:

Comparator byYearReverseAndMonth = compose(reverse(byYear), byMonth);
std::sort(values.begin(), values.end(), with(byYearReverseAndMonth));
  

编辑以回复评论:

     

当然,在编译时定义比较器和反向比较器的每个组合是。相反,可以在运行时收集所需的比较器以进行下一次排序,例如,在vector<Comparator>中。例如,比较器实例可以与表的列相关联:

vector<Comparator> comparators;
for (each selected column) {
    comparators.push_pack(comparatorFor(column));
}
     

然后可以将这些比较器组合成一个比较器,并在所有比较器上进行简单的循环:

Comparator composedComparator = comparators[0];
for (int i=1; i<comparators.size(); ++i) {
    composedComparator = compose(comparator, comparators[i]);
}

sort(v.begin(),v.end(),with(composedComparator));

这可能是什么样子的草图:

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

struct MyStruct
{
    int id;
    std::string currencyCode;
    int month;
    int year;
    int amount;
};

typedef std::function<int(const MyStruct& s0, const MyStruct& s1)> Comparator;
typedef std::function<bool(const MyStruct& s0, const MyStruct& s1)> Predicate;

template <typename T>
std::function<int(const T&, const T&)> compose(
    std::function<int(const T&, const T&)> c0,
    std::function<int(const T&, const T&)> c1)
{
    return[c0, c1](const T& t0, const T& t1) -> int
    {
        int r0 = c0(t0, t1);
        if (r0 != 0)
        {
            return r0;
        }
        return c1(t0, t1);
    };
}

template <typename T>
std::function<int(const T&, const T&)> reverse(
    std::function<int(const T&, const T&)> c)
{
    return[c](const T& t0, const T& t1) -> int
    {
        return -c(t0, t1);
    };
}

template <typename T>
std::function<bool(const T&, const T&)> with(
    std::function<int(const T&, const T&)> comparator)
{
    return[comparator](const T& t0, const T& t1) 
    {
        return comparator(t0, t1) < 0;
    };
}


void print(std::vector<MyStruct>& values)
{
    for (auto it = values.begin(); it != values.end(); ++it)
    {
        std::cout << (*it).month << "-"
            << (*it).year << " id "
            << (*it).id << std::endl;
    }
}

int main(int argc, char** argv)
{
    std::vector<MyStruct> values;

    MyStruct m;
    m.year = 1981; m.month = 1; m.id = 4;  values.push_back(m);
    m.year = 1980; m.month = 2; m.id = 5;  values.push_back(m);
    m.year = 1980; m.month = 4; m.id = 2;  values.push_back(m);
    m.year = 1980; m.month = 3; m.id = 3;  values.push_back(m);
    m.year = 1980; m.month = 4; m.id = 1;  values.push_back(m);

    std::cout << "Before sorting" << std::endl;
    print(values);

    Comparator byMonth = [](const MyStruct& s0, const MyStruct& s1)
    {
        if (s0.month < s1.month) return -1;
        if (s0.month > s1.month) return  1;
        return 0;
    };
    Comparator byYear = [](const MyStruct& s0, const MyStruct& s1)
    {
        if (s0.year < s1.year) return -1;
        if (s0.year > s1.year) return  1;
        return 0;
    };
    Comparator byId = [](const MyStruct& s0, const MyStruct& s1)
    {
        if (s0.id < s1.id) return -1;
        if (s0.id > s1.id) return  1;
        return 0;
    };

    Comparator byYearAndMonth = compose(byYear, byMonth);
    std::sort(values.begin(), values.end(), with(byYearAndMonth));

    std::cout << "After sorting by year and month:" << std::endl;
    print(values);


    Comparator byYearReverseAndMonth = compose(reverse(byYear), byMonth);
    std::sort(values.begin(), values.end(), with(byYearReverseAndMonth));

    std::cout << "After sorting by year reverse and month:" << std::endl;
    print(values);


    Comparator byYearAndMonthAndId = compose(byYearAndMonth, byId);
    std::sort(values.begin(), values.end(), with(byYearAndMonthAndId));

    std::cout << "After sorting by year and month and id:" << std::endl;
    print(values);

    return 0;
}

(对于潜在的失礼道歉,我是C ++新手)

答案 3 :(得分:5)

仅当前键的值相等时才使用下一个键:

rhs

在此示例中,条目的订购时间为[ ]( const MyStruct& lhs, const MyStruct& rhs ) { if(lhs.key1 != rhs.key1) return lhs.key1 < rhs.key1; if(lhs.key2 != rhs.key2) return lhs.key2 < rhs.key2; ... return lhs.keyN < rhs.keyN; } ,而不是key1,依此类推,直至key2

答案 4 :(得分:4)

按几个属性排序意味着定义重要性顺序。例如,按年份,月份:年份排序比月份更重要:

vector<MyStruct> values;
std::sort( values.begin( ), values.end( ), [ ]( const MyStruct& lhs, const MyStruct& rhs )
{
   return lhs.year < rhs.year || ( lhs.year == rhs.year && lhs.month < rhs.month );
});

答案 5 :(得分:3)

完成你想要的东西有点麻烦的方法是分几步完成(这也适用于C ++ 03)

struct ColumnOneDESC{...};
struct ColumnTwoASC{...};
...
std::stable_sort( values.begin(), values.end(), ColumnTwoASC());
std::stable_sort( values.begin(), values.end(), ColumnOneDESC());
...

对于cource,你可以使用std :: not或者这样的

来使它更通用