Сортування вектору користувацьких об'єктів


248

Як сортувати вектор, що містить власні (тобто визначені користувачем) об'єкти.
Можливо, стандартний алгоритм 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
та інше запитання / зауваження: якщо ви хочете мати кілька методів сортування (для різних атрибутів) у класі, спосіб перевантаження <оператора, ймовірно, не є варіантом, правда?
kluka

5
Класна річ - це також забезпечити метод оператор>. Це дозволить нам сортувати в зворотному порядку, наприклад:, std::sort(vec.begin(), vec.end(), greater<MyStruct>())який чистий і елегантний.
каппа

3
@Bovaz Вам потрібно #include <functional>використовувати "std :: veće ".
Нік Хартунг

4
@kappa: Де ви могли просто мати operator<та використовувати або, std::sort(vec.begin(), vec.end());або std::sort(vec.rbegin(), vec.rend());залежно від того, чи хочете ви мати порядок зростання чи спадання.
Піксельхімік

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 за включення #includes
Енн,

3
Щоб було зрозуміло, це призводить до порядку зростання; використовувати >замість того, <щоб отримати порядок зменшення.
бхалер

56

Ви можете використовувати функтор як третій аргумент 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для вхідних значень лише вказує, що ці вхідні значення незмінні.
счестер

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));

В 2. Якщо ви написали bool X_less(X const &l, X const &r) const { return l.i < r.i; }для порівняння , але constключові слова повинні бути видалені (як це не функція - член).
PolGraphic

@PolGraphic: Правильно - також у випадку 1.
Піксельхіміст

@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; });.
Піксельхіміст

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

1
Краще стати operator<членом класу (або структури), оскільки глобальний може використовувати захищені або приватні члени. Або вам слід зробити це другом у структурі C.
Кирило В. Лядвінський

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- виведення темпів).



3

У вашому класі ви можете перевантажувати оператора "<".

class MyClass
{
  bool operator <(const MyClass& rhs)
  {
    return this->key < rhs.key;
  }
}

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;
}

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);
}

якщо порівняння помилкове, воно зробить "swap".


Ні в якій мові ця збірка не буде
LF
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.