Сортировка вектора пользовательских объектов

249

Как можно сортировать вектор, содержащий пользовательские (то есть определяемые пользователем) объекты?
Вероятно, следует использовать стандартный алгоритм STL для сортировки вместе с предикатом (функцией или объектом функции), который будет работать с одним из полей (в качестве ключа для сортировки) в пользовательском объекте.
Я на правильном пути?

Анкур
источник
Возможная
копия

Ответы:

365

Простой пример использования std::sort

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};

struct less_than_key
{
    inline bool operator() (const MyStruct& struct1, const MyStruct& struct2)
    {
        return (struct1.key < struct2.key);
    }
};

std::vector < MyStruct > vec;

vec.push_back(MyStruct(4, "test"));
vec.push_back(MyStruct(3, "a"));
vec.push_back(MyStruct(2, "is"));
vec.push_back(MyStruct(1, "this"));

std::sort(vec.begin(), vec.end(), less_than_key());

Изменить: Как отметил Кирилл В. Лядвинский, вместо предоставления предиката сортировки, вы можете реализовать operator<для MyStruct:

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}

    bool operator < (const MyStruct& str) const
    {
        return (key < str.key);
    }
};

Использование этого метода означает, что вы можете просто отсортировать вектор следующим образом:

std::sort(vec.begin(), vec.end());

Edit2: Как предполагает Kappa, вы также можете сортировать вектор в порядке убывания, перегружая >оператор и немного меняя вызов сортировки:

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}

    bool operator > (const MyStruct& str) const
    {
        return (key > str.key);
    }
};

И вы должны назвать сортировку как:

std::sort(vec.begin(), vec.end(),greater<MyStruct>());
Алан
источник
2
Не могли бы вы объяснить, почему вы сделали встроенную функцию сравнения в структуре less_than_key (в первом примере)?
клука
2
и еще один вопрос / примечание: если вы хотите иметь несколько методов сортировки (для разных атрибутов) в классе, то перегрузка оператора <, вероятно, не вариант, верно?
клука
5
Классная вещь, чтобы обеспечить также оператор> метод. Это позволит нам сортировать в обратном порядке, как:, std::sort(vec.begin(), vec.end(), greater<MyStruct>())который является чистым и элегантным.
Каппа
3
@Bovaz Вам нужно #include <functional>использовать "std :: большее".
Ник Хартунг
4
@kappa: Где вы могли бы просто иметь operator<и использовать либо std::sort(vec.begin(), vec.end());или в std::sort(vec.rbegin(), vec.rend());зависимости от того, хотите ли вы иметь восходящий или нисходящий порядок.
Pixelchemist
182

В интересах освещения. Я выдвинул реализацию, используя лямбда-выражения .

C ++ 11

#include <vector>
#include <algorithm>

using namespace std;

vector< MyStruct > values;

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

C ++ 14

#include <vector>
#include <algorithm>

using namespace std;

vector< MyStruct > values;

sort( values.begin( ), values.end( ), [ ]( const auto& lhs, const auto& rhs )
{
   return lhs.key < rhs.key;
});
Бен Кроухерст
источник
21
дополнительные +1 за включение #include
Энн
3
Чтобы было ясно, это приводит к возрастанию; использовать >вместо того, <чтобы получить в порядке убывания.
bhaller
57

Вы можете использовать функтор в качестве третьего аргумента std::sortили вы можете определить operator<в своем классе.

struct X {
    int x;
    bool operator<( const X& val ) const { 
        return x < val.x; 
    }
};

struct Xgreater
{
    bool operator()( const X& lx, const X& rx ) const {
        return lx.x < rx.x;
    }
};

int main () {
    std::vector<X> my_vec;

    // use X::operator< by default
    std::sort( my_vec.begin(), my_vec.end() );

    // use functor
    std::sort( my_vec.begin(), my_vec.end(), Xgreater() );
}
Кирилл Васильевич Лядвинский
источник
4
почему нам нужно добавить constв конце подпись функции?
зубцы
4
Функция не меняет объект таким, какой он есть const.
Кирилл Васильевич Лядвинский
Если это так, то почему мы передаем «const X & val», я предполагаю, что передача значения как const функции заставляет функцию думать, что ее значение не будет изменено.
Прашант Бханаркар
1
@PrashantBhanarkar constКлючевое слово в конце подписи указывает, что operator()функция не изменяет экземпляр Xgreaterструктуры (которая обычно может иметь переменные-члены), тогда как указание constдля входных значений только указывает, что эти входные значения являются неизменяемыми.
schester
15

Сортировка такого vectorили любого другого применимого (изменяемого входного итератора) диапазона пользовательских объектов типа Xможет быть достигнута с использованием различных методов, особенно включая использование стандартных библиотечных алгоритмов, таких как

Поскольку большинство методов для получения относительного упорядочения Xэлементов уже опубликованы, я начну с некоторых заметок о том, «почему» и «когда» использовать различные подходы.

«Лучший» подход будет зависеть от разных факторов:

  1. Является ли сортировка диапазонов Xобъектов общей или редкой задачей (будут ли эти диапазоны сортироваться в нескольких разных местах в программе или пользователями библиотеки)?
  2. Является ли требуемая сортировка «естественной» (ожидаемой) или существует несколько способов сравнения типа с самим собой?
  3. Производительность - это проблема, или диапазоны сортировки Xобъектов должны быть надежными?

Если сортировка диапазонов Xявляется распространенной задачей, и ожидаемая сортировка должна ожидаться (то есть, Xпросто оборачивает одно фундаментальное значение), то, вероятно, будет идти на перегрузкуoperator< так как она позволяет сортировку без какого-либо размытия (например, правильную передачу соответствующих компараторов) и многократно дает ожидаемую полученные результаты.

Если сортировка является распространенной задачей или, вероятно, потребуется в разных контекстах, но есть несколько критериев, которые можно использовать для сортировки Xобъектов, я бы выбрал Functors (перегруженные operator()функции пользовательских классов) или указатели на функции (т.е. один функтор / функция для лексического упорядочения и еще один для естественного упорядочения).

Если сортировка диапазонов типов Xнеобычна или маловероятна в других контекстах, я склонен использовать лямбды вместо того, чтобы загромождать любое пространство имен большим количеством функций или типов.

Это особенно верно, если сортировка не является «чистой» или «естественной» в некотором роде. Вы можете легко понять логику упорядочения, если посмотреть на лямбду, которая применяется на месте, тогда как operator<на первый взгляд она непрозрачна, и вам придется поискать определение, чтобы знать, какая логика упорядочения будет применяться.

Однако обратите внимание, что одно operator<определение - это одна точка отказа, тогда как несколько лямб - это несколько точек отказа и требуют большей осторожности.

Если определение operator< где недоступно, где сортировка выполнена / шаблон сортировки скомпилирован, компилятор может быть вынужден сделать вызов функции при сравнении объектов, вместо того, чтобы включать логику упорядочения, которая может быть серьезным недостатком (по крайней мере, когда оптимизация времени соединения / генерация кода не применяется).

Способы достижения сопоставимости class Xдля использования стандартных алгоритмов сортировки библиотеки

Пусть std::vector<X> vec_X;иstd::vector<Y> vec_Y;

1. Перегрузите T::operator<(T)или operator<(T, T)и используйте стандартные библиотечные шаблоны, которые не ожидают функции сравнения.

Либо элемент перегрузки operator<:

struct X {
  int i{}; 
  bool operator<(X const &r) const { return i < r.i; } 
};
// ...
std::sort(vec_X.begin(), vec_X.end());

или бесплатно operator<:

struct Y {
  int j{}; 
};
bool operator<(Y const &l, Y const &r) { return l.j < r.j; }
// ...
std::sort(vec_Y.begin(), vec_Y.end());

2. Используйте указатель функции с пользовательской функцией сравнения в качестве параметра функции сортировки.

struct X {
  int i{};  
};
bool X_less(X const &l, X const &r) { return l.i < r.i; }
// ...
std::sort(vec_X.begin(), vec_X.end(), &X_less);

3. Создайте bool operator()(T, T)перегрузку для пользовательского типа, которая может быть передана как функтор сравнения.

struct X {
  int i{};  
  int j{};
};
struct less_X_i
{
    bool operator()(X const &l, X const &r) const { return l.i < r.i; }
};
struct less_X_j
{
    bool operator()(X const &l, X const &r) const { return l.j < r.j; }
};
// sort by i
std::sort(vec_X.begin(), vec_X.end(), less_X_i{});
// or sort by j
std::sort(vec_X.begin(), vec_X.end(), less_X_j{});

Эти определения функциональных объектов могут быть написаны немного более обобщенно, используя C ++ 11 и шаблоны:

struct less_i
{ 
    template<class T, class U>
    bool operator()(T&& l, U&& r) const { return std::forward<T>(l).i < std::forward<U>(r).i; }
};

который может быть использован для сортировки любого типа с iподдержкой членов <.

4. Передайте анонимное закрытие (лямбда) в качестве параметра сравнения в функции сортировки.

struct X {
  int i{}, j{};
};
std::sort(vec_X.begin(), vec_X.end(), [](X const &l, X const &r) { return l.i < r.i; });

Где C ++ 14 обеспечивает еще более общее лямбда-выражение:

std::sort(a.begin(), a.end(), [](auto && l, auto && r) { return l.i < r.i; });

который можно обернуть в макрос

#define COMPARATOR(code) [](auto && l, auto && r) -> bool { return code ; }

сделать обычное создание компаратора довольно плавным:

// sort by i
std::sort(v.begin(), v.end(), COMPARATOR(l.i < r.i));
// sort by j
std::sort(v.begin(), v.end(), COMPARATOR(l.j < r.j));
Pixelchemist
источник
В 2. случае, если вы написали bool X_less(X const &l, X const &r) const { return l.i < r.i; }для компаратора, но constключевые слова должны быть удалены (так как это не функция-член).
PolGraphic
@PolGraphic: Правильно - и в случае 1 тоже.
Pixelchemist
@Pixelchemist, как бы я использовал (4.) лямбда-подход, когда он не используется std::sortили похож, но нужен экземпляр Compare, например, при создании экземпляра std::set?
azrdev
1
@azrdev: Шаблон функции, который фиксирует тип замыкания, чтобы передать его в качестве параметра шаблона для установки: template<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }который можно использовать как auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });.
Pixelchemist
14

Ты на правильном пути. std::sortбудет использовать в operator<качестве функции сравнения по умолчанию. Таким образом, для сортировки ваших объектов вам придется либо перегружать, bool operator<( const T&, const T& )либо предоставлять функтор, который выполняет сравнение, примерно так:

 struct C {
    int i;
    static bool before( const C& c1, const C& c2 ) { return c1.i < c2.i; }
 };

 bool operator<( const C& c1, const C& c2 ) { return c1.i > c2.i; }

 std::vector<C> values;

 std::sort( values.begin(), values.end() ); // uses operator<
 std::sort( values.begin(), values.end(), C::before );

Преимущество использования функтора заключается в том, что вы можете использовать функцию с доступом к закрытым членам класса.

xtofl
источник
Пропустил это: предоставить оператор функции члена <.
xtofl
1
Лучше сделать operator<член класса (или структуры), потому что глобальный может использовать защищенные или закрытые члены. Или вы должны сделать это другом структуры С.
Кирилл В. Лядвинский
5

Мне было любопытно, есть ли какое-либо измеримое влияние на производительность между различными способами вызова std :: sort, поэтому я создал этот простой тест:

$ cat sort.cpp
#include<algorithm>
#include<iostream>
#include<vector>
#include<chrono>

#define COMPILER_BARRIER() asm volatile("" ::: "memory");

typedef unsigned long int ulint;

using namespace std;

struct S {
  int x;
  int y;
};

#define BODY { return s1.x*s2.y < s2.x*s1.y; }

bool operator<( const S& s1, const S& s2 ) BODY
bool Sgreater_func( const S& s1, const S& s2 ) BODY

struct Sgreater {
  bool operator()( const S& s1, const S& s2 ) const BODY
};

void sort_by_operator(vector<S> & v){
  sort(v.begin(), v.end());
}

void sort_by_lambda(vector<S> & v){
  sort(v.begin(), v.end(), []( const S& s1, const S& s2 ) BODY );
}

void sort_by_functor(vector<S> &v){
  sort(v.begin(), v.end(), Sgreater());
}

void sort_by_function(vector<S> &v){
  sort(v.begin(), v.end(), &Sgreater_func);
}

const int N = 10000000;
vector<S> random_vector;

ulint run(void foo(vector<S> &v)){
  vector<S> tmp(random_vector);
  foo(tmp);
  ulint checksum = 0;
  for(int i=0;i<tmp.size();++i){
     checksum += i *tmp[i].x ^ tmp[i].y;
  }
  return checksum;
}

void measure(void foo(vector<S> & v)){

ulint check_sum = 0;

  // warm up
  const int WARMUP_ROUNDS = 3;
  const int TEST_ROUNDS = 10;

  for(int t=WARMUP_ROUNDS;t--;){
    COMPILER_BARRIER();
    check_sum += run(foo);
    COMPILER_BARRIER();
  }

  for(int t=TEST_ROUNDS;t--;){
    COMPILER_BARRIER();
    auto start = std::chrono::high_resolution_clock::now();
    COMPILER_BARRIER();
    check_sum += run(foo);
    COMPILER_BARRIER();
    auto end = std::chrono::high_resolution_clock::now();
    COMPILER_BARRIER();
    auto duration_ns = std::chrono::duration_cast<std::chrono::duration<double>>(end - start).count();

    cout << "Took " << duration_ns << "s to complete round" << endl;
  }

  cout << "Checksum: " << check_sum << endl;
}

#define M(x) \
  cout << "Measure " #x " on " << N << " items:" << endl;\
  measure(x);

int main(){
  random_vector.reserve(N);

  for(int i=0;i<N;++i){
    random_vector.push_back(S{rand(), rand()});
  }

  M(sort_by_operator);
  M(sort_by_lambda);
  M(sort_by_functor);
  M(sort_by_function);
  return 0;
}

Он создает случайный вектор, а затем измеряет, сколько времени требуется для его копирования и сортировки (и вычисления некоторой контрольной суммы, чтобы избежать слишком энергичного удаления мертвого кода).

Я компилировал с g ++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)

$ g++ -O2 -o sort sort.cpp && ./sort

Вот результаты:

Measure sort_by_operator on 10000000 items:
Took 0.994285s to complete round
Took 0.990162s to complete round
Took 0.992103s to complete round
Took 0.989638s to complete round
Took 0.98105s to complete round
Took 0.991913s to complete round
Took 0.992176s to complete round
Took 0.981706s to complete round
Took 0.99021s to complete round
Took 0.988841s to complete round
Checksum: 18446656212269526361
Measure sort_by_lambda on 10000000 items:
Took 0.974274s to complete round
Took 0.97298s to complete round
Took 0.964506s to complete round
Took 0.96899s to complete round
Took 0.965773s to complete round
Took 0.96457s to complete round
Took 0.974286s to complete round
Took 0.975524s to complete round
Took 0.966238s to complete round
Took 0.964676s to complete round
Checksum: 18446656212269526361
Measure sort_by_functor on 10000000 items:
Took 0.964359s to complete round
Took 0.979619s to complete round
Took 0.974027s to complete round
Took 0.964671s to complete round
Took 0.964764s to complete round
Took 0.966491s to complete round
Took 0.964706s to complete round
Took 0.965115s to complete round
Took 0.964352s to complete round
Took 0.968954s to complete round
Checksum: 18446656212269526361
Measure sort_by_function on 10000000 items:
Took 1.29942s to complete round
Took 1.3029s to complete round
Took 1.29931s to complete round
Took 1.29946s to complete round
Took 1.29837s to complete round
Took 1.30132s to complete round
Took 1.3023s to complete round
Took 1.30997s to complete round
Took 1.30819s to complete round
Took 1.3003s to complete round
Checksum: 18446656212269526361

Похоже, что все параметры, за исключением передачи указателя на функцию, очень похожи, а передача указателя на функцию вызывает штраф + 30%.

Кроме того, похоже, что оператор <версия работает на ~ 1% медленнее (я повторил тест несколько раз, и эффект сохраняется), что немного странно, так как предполагает, что сгенерированный код отличается (мне не хватает навыков для анализа --save- вывод временных данных).

qbolec
источник
4

Да, std::sort()с третьим параметром (функцией или объектом) было бы проще. Пример: http://www.cplusplus.com/reference/algorithm/sort/

swatkat
источник
Не совсем ответ только по ссылке, но хотя бы однострочный пример был бы полезен.
Манос Николаидис
3

В вашем классе вы можете перегрузить оператор "<".

class MyClass
{
  bool operator <(const MyClass& rhs)
  {
    return this->key < rhs.key;
  }
}
BobbyShaftoe
источник
3

Ниже приведен код с использованием лямбды

#include "stdafx.h"
#include <vector>
#include <algorithm>

using namespace std;

struct MyStruct
{
    int key;
    std::string stringValue;

    MyStruct(int k, const std::string& s) : key(k), stringValue(s) {}
};

int main()
{
    std::vector < MyStruct > vec;

    vec.push_back(MyStruct(4, "test"));
    vec.push_back(MyStruct(3, "a"));
    vec.push_back(MyStruct(2, "is"));
    vec.push_back(MyStruct(1, "this"));

    std::sort(vec.begin(), vec.end(), 
        [] (const MyStruct& struct1, const MyStruct& struct2)
        {
            return (struct1.key < struct2.key);
        }
    );
    return 0;
}
Sathwick
источник
1
    // sort algorithm example
    #include <iostream>     // std::cout
    #include <algorithm>    // std::sort
    #include <vector>       // std::vector
    using namespace std;
    int main () {
        char myints[] = {'F','C','E','G','A','H','B','D'};
        vector<char> myvector (myints, myints+8);               // 32 71 12 45 26 80 53 33
        // using default comparison (operator <):
        sort (myvector.begin(), myvector.end());           //(12 32 45 71)26 80 53 33
        // print out content:
        cout << "myvector contains:";
        for (int i=0; i!=8; i++)
            cout << ' ' <<myvector[i];
        cout << '\n';
        system("PAUSE");
    return 0;
    }
Амин Аломаиси
источник
1

Вы можете использовать пользовательский класс компаратора.

class comparator
{
    int x;
    bool operator()( const comparator &m,  const comparator &n )
    { 
       return m.x<n.x;
    }
 }

источник
0

Для сортировки вектора вы можете использовать алгоритм sort ().

sort(vec.begin(),vec.end(),less<int>());

Третий используемый параметр может быть больше или меньше, или также может использоваться любая функция или объект. Однако оператор по умолчанию - <, если вы оставите третий параметр пустым.

// using function as comp
std::sort (myvector.begin()+4, myvector.end(), myfunction);
bool myfunction (int i,int j) { return (i<j); }

// using object as comp
std::sort (myvector.begin(), myvector.end(), myobject);
Прашант Шубхам
источник
0
typedef struct Freqamp{
    double freq;
    double amp;
}FREQAMP;

bool struct_cmp_by_freq(FREQAMP a, FREQAMP b)
{
    return a.freq < b.freq;
}

main()
{
    vector <FREQAMP> temp;
    FREQAMP freqAMP;

    freqAMP.freq = 330;
    freqAMP.amp = 117.56;
    temp.push_back(freqAMP);

    freqAMP.freq = 450;
    freqAMP.amp = 99.56;
    temp.push_back(freqAMP);

    freqAMP.freq = 110;
    freqAMP.amp = 106.56;
    temp.push_back(freqAMP);

    sort(temp.begin(),temp.end(), struct_cmp_by_freq);
}

если сравнение ложно, это сделает "обмен".

брюс
источник
Ни на каком языке это не скомпилируется.
LF