カスタムオブジェクトのベクターの並べ替え


248

カスタム(つまり、ユーザー定義)オブジェクトを含むベクターを並べ替える方法を教えてください。
おそらく、カスタムオブジェクトのフィールドの1つ(並べ替えのキーとして)を操作する述語(関数または関数オブジェクト)と共に標準の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());

編集: Kirill V. Lyadvinskyが指摘したように、ソート述語を指定する代わりに、operator<for を実装できます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が示唆しているように、>演算子をオーバーロードし、sortの呼び出しを少し変更することで、ベクトルを降順で並べ替えることもできます。

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

そして、あなたはsortを次のように呼び出す必要があります:

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

2
(最初の)struct less_than_keyの例で比較関数をインラインにした理由を説明できますか?
kluka 2013年

2
そして、別の質問/注:クラスに複数のソート方法(異なる属性用)が必要な場合、<演算子をオーバーロードする方法はおそらくオプションではありませんよね?
kluka

5
すばらしいのは、operator>メソッドも提供することです。これによりstd::sort(vec.begin(), vec.end(), greater<MyStruct>())、次のような逆の順序で並べ替えることができます。
カッパ2014年

3
@Bovaz #include <functional>"std :: greater"を使用する必要があります。
Nick Hartung 2015

4
@kappa:operator<どちらを使用するかstd::sort(vec.begin(), vec.end());std::sort(vec.rbegin(), vec.rend());昇順または降順のどちらを使用するかによって異なります。
Pixelchemist 2016年

181

報道のために。ラムダ式を使用した実装を提案しました。

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
#includeなどのための余分な1
アン

3
明確にするために、これは昇順になります。の>代わりに使用<して、降順で取得します。
バラー2017

56

functorをの3番目の引数として使用するstd::sortoperator<、クラスで定義することができます。

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
キリルV.リヤドビンスキー2013

その場合、 "const X&val"を渡す理由は、値をconstとして関数に渡すと、関数はその値が変更されないと考えていることを想定しているためです。
Prashant Bhanarkar

1
@PrashantBhanarkar constシグネチャの最後にあるキーワードは、operator()関数がXgreater構造体のインスタンスを変更しないことを指定します(これは一般にメンバー変数を持つ可能性があります)const
schester、

15

vectorタイプのカスタムオブジェクトのそのようなまたはその他の適用可能な(可変入力反復子)範囲の並べ替えは、Xさまざまな方法を使用して、特に次のような標準ライブラリアルゴリズムの使用を含めて達成できます。

X要素の相対的な順序を取得するためのほとんどの手法は既に投稿されているので、さまざまなアプローチを使用する「理由」と「時期」についてのメモから始めます。

「最良の」アプローチは、さまざまな要因に依存します。

  1. Xオブジェクトの範囲の並べ替えは一般的なタスクですか、それともまれなタスクですか?
  2. 必要な並べ替えは「自然」(期待される)ですか、それとも型をそれ自体と比較できる複数の方法がありますか?
  3. パフォーマンスに問題はありXますか、それともオブジェクトの範囲のソートは絶対確実でなければなりませんか?

範囲の並べ替えがX一般的なタスクであり、達成された並べ替えが期待される(つまりX、単一の基本値をラップする)場合operator<、ファズなしで並べ替えを可能にし(適切なコンパレータを正しく渡すなど)、繰り返し期待される結果が得られるため、おそらくオーバーロードになります結果。

並べ替えが一般的なタスクであるか、異なるコンテキストで必要になる可能性が高いが、Xオブジェクトの並べ替えに使用できる基準が複数ある場合は、operator()ファンクター(カスタムクラスのオーバーロードされた関数)または関数ポインター(つまり、1つのファンクター/関数)を使用します語彙の順序付けと自然順序付けの別の順序)。

タイプのソート範囲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.匿名のクロージャー(lambda)を比較パラメーターとしてソート関数に渡します。

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 2016

@Pixelchemistどうやって(4.)ラムダアプローチを使用しないのstd::sortか、または類似していないがCompare、たとえばstd::set
azrdev 2018年

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

ファンクタを使用する利点は、クラスのプライベートメンバーにアクセスできる関数を使用できることです。


その1つを見逃しました:メンバー関数operator <を提供します。
xtofl 2009

1
operator<グローバルなメンバーは保護されたメンバーまたはプライベートメンバーを使用できるため、クラス(または構造体)のメンバーを作成する方が適切です。それとも、それ構造体Cの友人にする必要があります
キリルV. Lyadvinsky

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%のペナルティが発生します。

また、operator <バージョンは〜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>());

使用する3番目のパラメーターは多かれ少なかれ、任意の関数またはオブジェクトも使用できます。ただし、3番目のパラメーターを空のままにすると、デフォルトの演算子は<になります。

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

比較がfalseの場合、「スワップ」します。


どの言語でもこれはコンパイルされません。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.