С++ 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
)?
Ответы
Ответ 1
Просто измените компаратор. Предположим, вы хотите заказать год, затем месяц, затем по количеству, затем:
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
operator<
делает лексикографическое сравнение, поэтому нет необходимости сворачивать свои собственные (и рисковать ошибиться). Чтобы выполнить нисходящий порядок для атрибута, просто замените lhs
и rhs
на этот атрибут.
Ответ 2
Ваша потребность в выборе порядка сортировки во время выполнения отличается от большинства подобных вопросов (и нескольких полученных вами ответов на коленный рефлекс). Я предлагаю вам дать каждому полю номер 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
или ссылается на них как .first
/.second
, если вы предпочитаете).
Еще лучше найти графическую библиотеку с достойным виджетами сетки/таблицы, который выполняет сортировку внутри.
Ответ 3
Решение этого путем присвоения идентификаторов полям имеет несколько недостатков:
- Случаи не могут быть покрыты (как указано
throw
)
- Для сортировки в обратном порядке требуется дополнительный флаг
- Это не очевидно из кода, идентификатор которого обозначает, какое поле
- Это не легко расширяемое (вам всегда нужно обновлять сопоставления идентификаторов)
В качестве альтернативы вы можете определить "компараторы", которые возвращают -1
, 0
или 1
, в зависимости от того, меньше ли первый аргумент, равный или больший, чем второй. Затем эти компараторы могут быть объединены и полностью отформатированы и использованы как предикат сортировки.
Учитывая набор этих компараторов для полей вашей структуры, вы можете составить их следующим образом:
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;
}
(Извинения за потенциальный faux pas, я новичок С++)
Ответ 4
Используйте следующую клавишу, только если значения текущего ключа равны:
[ ]( 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
и т.д. до keyN
.
Ответ 5
Заказ нескольких атрибутов означает определение порядка важности. Например, упорядочение по годам, месяцам: год более важно, чем месяц:
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 );
});
Ответ 6
Немного громоздкий способ выполнить то, что вы ищете, сделать это несколькими шагами (это тоже будет работать на С++ 03)
struct ColumnOneDESC{...};
struct ColumnTwoASC{...};
...
std::stable_sort( values.begin(), values.end(), ColumnTwoASC());
std::stable_sort( values.begin(), values.end(), ColumnOneDESC());
...
И в курсе вы можете сделать его более общим с помощью std:: not или такого