Sắp xếp một vectơ của các đối tượng tùy chỉnh


248

Làm thế nào để đi về sắp xếp một vectơ có chứa các đối tượng tùy chỉnh (tức là người dùng xác định).
Có lẽ, tiêu chuẩn thuật toán STL loại cùng với một vị (một chức năng hay một đối tượng chức năng) mà sẽ hoạt động trên một trong những lĩnh vực (như là một chìa khóa để phân loại) trong đối tượng tùy chỉnh nên được sử dụng.
Có phải tôi đang trên đường ray bên phải không?


Câu trả lời:


365

Một ví dụ đơn giản sử dụng 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());

Chỉnh sửa: Như Kirill V. Lyadvinsky đã chỉ ra, thay vì cung cấp một vị từ sắp xếp, bạn có thể thực hiện operator<cho 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);
    }
};

Sử dụng phương pháp này có nghĩa là bạn có thể chỉ cần sắp xếp các vectơ như sau:

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

Edit2: Như Kappa gợi ý, bạn cũng có thể sắp xếp vectơ theo thứ tự giảm dần bằng cách nạp chồng >toán tử và thay đổi lệnh gọi sắp xếp một chút:

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

Và bạn nên gọi sắp xếp là:

std::sort(vec.begin(), vec.end(),greater<MyStruct>());

2
Bạn có thể giải thích lý do tại sao bạn thực hiện chức năng so sánh trong struct less_than_key (trong ví dụ đầu tiên) nội tuyến không?
kluka

2
và một câu hỏi / lưu ý khác: nếu một người muốn có nhiều phương thức sắp xếp (cho các thuộc tính khác nhau) trong một lớp thì cách quá tải toán tử <có lẽ không phải là một tùy chọn, phải không?
kluka

5
Một điều thú vị là cung cấp cả toán tử> phương thức. Điều này sẽ cho phép chúng tôi sắp xếp theo thứ tự ngược lại như : std::sort(vec.begin(), vec.end(), greater<MyStruct>()), sạch sẽ và thanh lịch.
kappa

3
@Bovaz Bạn cần #include <functional>sử dụng "std :: lớn hơn".
Nick Hartung

4
@kappa: Nơi bạn có thể có operator<và sử dụng std::sort(vec.begin(), vec.end());hoặc std::sort(vec.rbegin(), vec.rend());tùy thuộc vào việc bạn muốn có thứ tự tăng dần hay giảm dần.
Nhà hóa học

181

Trong lợi ích của bảo hiểm. Tôi đưa ra một triển khai bằng cách sử dụng biểu thức lambda .

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
thêm +1 để bao gồm #incolees
Anne

3
Để rõ ràng, điều này dẫn đến thứ tự tăng dần; sử dụng >thay vì <để có được thứ tự giảm dần.
bhaller

56

Bạn có thể sử dụng functor làm đối số thứ ba std::sorthoặc bạn có thể định nghĩa operator<trong lớp.

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
Tại sao chúng ta cần thêm constvào cuối chữ ký hàm?
ngạnh

4
Hàm không thay đổi đối tượng const.
Kirill V. Lyadvinsky

Nếu đó là trường hợp thì tại sao chúng ta chuyển "const X & val", tôi giả sử rằng việc chuyển giá trị dưới dạng const cho hàm làm cho hàm nghĩ rằng giá trị của nó sẽ không bị thay đổi.
Prashant Bhanarkar

1
@PrashantBhanarkar constTừ khóa ở cuối chữ ký xác định rằng operator()hàm không thay đổi thể hiện của Xgreaterstruct (nói chung có thể có các biến thành viên), trong khi chỉ ra constcác giá trị đầu vào chỉ xác định rằng các giá trị đầu vào đó là bất biến.
giả

15

Sắp xếp một vectorhoặc bất kỳ phạm vi áp dụng (trình lặp đầu vào có thể thay đổi) nào khác Xcó thể đạt được bằng cách sử dụng các phương pháp khác nhau, đặc biệt là sử dụng các thuật toán thư viện tiêu chuẩn như

Vì hầu hết các kỹ thuật, để có được thứ tự tương đối của Xcác yếu tố, đã được đăng, tôi sẽ bắt đầu bằng một số lưu ý về "tại sao" và "khi nào" để sử dụng các phương pháp khác nhau.

Cách tiếp cận "tốt nhất" sẽ phụ thuộc vào các yếu tố khác nhau:

  1. Là sắp xếp các phạm vi của Xcác đối tượng là một nhiệm vụ phổ biến hoặc hiếm gặp (các phạm vi như vậy sẽ được sắp xếp một vị trí khác nhau trong chương trình hoặc bởi người dùng thư viện)?
  2. Là sự sắp xếp cần thiết "tự nhiên" (dự kiến) hoặc có nhiều cách để loại có thể được so sánh với chính nó?
  3. Là hiệu suất là một vấn đề hoặc nên sắp xếp phạm vi của Xcác đối tượng là hoàn hảo?

Nếu sắp xếp các phạm vi Xlà một nhiệm vụ phổ biến và sắp xếp đạt được sẽ được dự kiến ​​(nghĩa là Xchỉ bao bọc một giá trị cơ bản duy nhất) thì có thể sẽ bị quá tải operator<vì nó cho phép sắp xếp mà không có bất kỳ lỗi nào (như chuyển chính xác các bộ so sánh phù hợp) và liên tục mang lại kết quả mong đợi các kết quả.

Nếu sắp xếp là một nhiệm vụ chung hoặc có thể được yêu cầu trong các ngữ cảnh khác nhau, nhưng có nhiều tiêu chí có thể được sử dụng để sắp xếp Xcác đối tượng, tôi sẽ tìm Functor ( operator()hàm quá tải của các lớp tùy chỉnh) hoặc con trỏ hàm (tức là một hàm functor / hàm để đặt hàng từ vựng và một số khác cho trật tự tự nhiên).

Nếu sắp xếp phạm vi loại Xkhông phổ biến hoặc không có khả năng trong các bối cảnh khác, tôi có xu hướng sử dụng lambdas thay vì làm lộn xộn bất kỳ không gian tên nào có nhiều chức năng hoặc loại.

Điều này đặc biệt đúng nếu cách sắp xếp không "rõ ràng" hoặc "tự nhiên" theo một cách nào đó. Bạn có thể dễ dàng nhận được logic đằng sau việc đặt hàng khi nhìn vào lambda được áp dụng tại chỗ trong khi đó operator<là điều khó hiểu ngay từ cái nhìn đầu tiên và bạn phải xem định nghĩa để biết logic nào sẽ được áp dụng.

Tuy nhiên, lưu ý rằng một operator<định nghĩa duy nhất là một điểm thất bại trong khi nhiều lambas là nhiều điểm thất bại và cần thận trọng hơn.

Nếu định nghĩa operator<không khả dụng khi sắp xếp xong / mẫu sắp xếp được biên dịch, trình biên dịch có thể bị buộc phải thực hiện một cuộc gọi hàm khi so sánh các đối tượng, thay vì nội tuyến logic có thể là một nhược điểm nghiêm trọng (ít nhất là khi tối ưu hóa thời gian liên kết / tạo mã không được áp dụng).

Các cách để đạt được sự so sánh class Xđể sử dụng các thuật toán sắp xếp thư viện tiêu chuẩn

Hãy để std::vector<X> vec_X;std::vector<Y> vec_Y;

1. Quá tải T::operator<(T)hoặc operator<(T, T)sử dụng các mẫu thư viện tiêu chuẩn không mong đợi chức năng so sánh.

Thành viên quá tải operator<:

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

hoặc miễn phí 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. Sử dụng một con trỏ hàm với chức năng so sánh tùy chỉnh làm tham số chức năng sắp xếp.

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. Tạo bool operator()(T, T)quá tải cho một loại tùy chỉnh có thể được thông qua dưới dạng functor so sánh.

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ác định nghĩa đối tượng hàm có thể được viết chung chung hơn một chút bằng C ++ 11 và các mẫu:

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

có thể được sử dụng để sắp xếp bất kỳ loại nào với sự ihỗ trợ của thành viên <.

4. Truyền một bao ẩn danh (lambda) làm tham số so sánh cho các hàm sắp xếp.

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

Trong đó C ++ 14 cho phép biểu thức lambda chung hơn nữa:

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

mà có thể được bọc trong một macro

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

làm cho việc tạo so sánh thông thường khá trơn tru:

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

Trong 2. trường hợp bạn đã viết bool X_less(X const &l, X const &r) const { return l.i < r.i; }để so sánh nhưng các consttừ khóa nên được loại bỏ (vì nó không phải là một hàm thành viên).
PolGpson

@PolGpson: Đúng - trong trường hợp 1 là tốt.
Nhà hóa học 29/8/2016

@Pixelchemist tôi sẽ sử dụng phương pháp lambda (4.) như thế nào khi không sử dụng std::sorthoặc tương tự, nhưng cần một ví dụ Compare, ví dụ như khi khởi tạo a std::set?
azrdev

1
@azrdev: Một mẫu hàm nắm bắt kiểu đóng để truyền nó dưới dạng tham số mẫu cần đặt: template<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }có thể được sử dụng như thế nào auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });.
Nhà hóa học

14

Bạn đang đi đúng hướng. std::sortsẽ sử dụng operator<làm chức năng so sánh theo mặc định. Vì vậy, để sắp xếp các đối tượng của bạn, bạn sẽ phải quá tải bool operator<( const T&, const T& )hoặc cung cấp một functor thực hiện so sánh, giống như thế này:

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

Ưu điểm của việc sử dụng functor là bạn có thể sử dụng một hàm có quyền truy cập vào các thành viên riêng của lớp.


Bỏ lỡ cái đó: cung cấp một toán tử hàm thành viên <.
xtofl

1
Tốt hơn là tạo operator<một thành viên của lớp (hoặc struct), bởi vì toàn cầu có thể sử dụng các thành viên được bảo vệ hoặc tư nhân. Hoặc bạn nên biến nó thành bạn của struct C.
Kirill V. Lyadvinsky

5

Tôi tò mò liệu có bất kỳ tác động có thể đo lường nào đối với hiệu suất giữa các cách khác nhau mà người ta có thể gọi std :: sort, vì vậy tôi đã tạo ra thử nghiệm đơn giản này:

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

Những gì nó làm là nó tạo ra một vectơ ngẫu nhiên, và sau đó đo xem cần bao nhiêu thời gian để sao chép nó và sắp xếp bản sao của nó (và tính toán một số tổng kiểm tra để tránh loại bỏ mã chết quá mạnh mẽ).

Tôi đã biên dịch với g ++ (GCC) 7.2.1 20170829 (Mũ đỏ 7.2.1-1)

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

Đây là kết quả:

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

Có vẻ như tất cả các tùy chọn ngoại trừ việc truyền con trỏ hàm rất giống nhau và việc truyền con trỏ hàm gây ra hình phạt + 30%.

Có vẻ như toán tử <phiên bản chậm hơn ~ 1% (Tôi đã lặp lại thử nghiệm nhiều lần và hiệu ứng vẫn tồn tại), điều này hơi lạ vì nó cho thấy mã được tạo ra là khác nhau (tôi thiếu kỹ năng phân tích --save- đầu ra temps).



3

Trong lớp của bạn, bạn có thể quá tải toán tử "<".

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

3

Dưới đây là mã sử dụng lambdas

#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

Bạn có thể sử dụng lớp so sánh do người dùng định nghĩa.

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

0

Để sắp xếp một vectơ, bạn có thể sử dụng thuật toán sort () trong.

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

Tham số thứ ba được sử dụng có thể lớn hơn hoặc ít hơn hoặc bất kỳ chức năng hoặc đối tượng nào cũng có thể được sử dụng. Tuy nhiên, toán tử mặc định là <nếu bạn để trống tham số thứ ba.

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

nếu so sánh là sai, nó sẽ thực hiện "hoán đổi".


Không có ngôn ngữ này sẽ biên dịch.
LF
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.