Özel nesnelerin bir vektörünü sıralama


249

Özel (örn. Kullanıcı tanımlı) nesneler içeren bir vektörü sıralama konusunda bir kişi nasıl yapılır.
Muhtemelen, özel nesnedeki alanlardan birinde (sıralama için bir anahtar olarak) çalışacak bir yüklem (işlev veya işlev nesnesi) ile birlikte standart STL algoritması sıralaması kullanılmalıdır.
Ben doğru yolda mıyım?


Yanıtlar:


365

Kullanarak basit bir örnek 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());

Düzenleme: Kirill V. Lyadvinsky belirttiği gibi, bir çeşit yüklemi tedarik yerine, sen uygulayabilir operator<için 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);
    }
};

Bu yöntemi kullanmak, vektörü aşağıdaki gibi sıralayabileceğiniz anlamına gelir:

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

Edit2: Kappa'nın önerdiği gibi, bir >operatörü aşırı yükleyerek ve biraz sıralama çağrısını değiştirerek vektörü azalan sırada sıralayabilirsiniz:

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

Ve sort'i şöyle çağırmalısınız:

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

2
Karşılaştırma işlevini less_than_key (ilk örnekte) satır içi yapısında neden yaptığınızı açıklayabilir misiniz?
kluka

2
ve başka bir soru / not: Bir sınıfta birden fazla sıralama yöntemi (farklı özellikler için) kullanmak istiyorsanız, <işlecini aşırı yükleme yöntemi muhtemelen bir seçenek değildir, değil mi?
kluka

5
Serin bir şey de operatör> yöntemi sağlamaktır. Bu, aşağıdaki gibi ters sırada sıralamamızı sağlayacaktır: std::sort(vec.begin(), vec.end(), greater<MyStruct>())ki bu temiz ve zariftir.
kappa

3
@Bovaz #include <functional>"std :: bigger " kullanmanız gerekir .
Nick Hartung

4
@kappa: Sadece olabilir Nerede operator<kullanımını ya ve std::sort(vec.begin(), vec.end());ya std::sort(vec.rbegin(), vec.rend());siz artan veya azalan sırayla olmasını istemediğinize bağlı.
Pixelchemist

182

Kapsam çıkarına. Lambda ifadeleri kullanarak bir uygulama ortaya koydum .

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
#includes dahil olmak üzere ekstra +1
Anne

3
Açık olmak gerekirse, bu artan düzen ile sonuçlanır; azalan düzen >yerine kullanın <.
bhaller

57

Functor öğesini üçüncü argüman olarak kullanabilirsiniz std::sortveya operator<sınıfınızda tanımlayabilirsiniz .

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
constfonksiyon imzasının sonuna neden eklememiz gerekiyor ?
çatalları

4
Fonksiyon nesneyi değiştirmez, öyle olur const.
Kirill V. Lyadvinsky

Bu durumda neden "const X & val" iletilirse, değerin bir işlev olarak const olarak iletilmesinin işlevi değerinin değiştirilmeyeceğini düşünmesini sağladığını varsayıyorum.
Prashant Bhanarkar

1
@PrashantBhanarkar İmzanın constsonundaki anahtar sözcük, operator()işlevin Xgreateryapı örneğini (genel olarak üye değişkenlere sahip olabilir) değiştirmediğini belirtirken const, giriş değerlerinin belirtilmesi yalnızca bu giriş değerlerinin değiştirilemez olduğunu belirtir.
schester

15

Bu tür bir vectorveya herhangi bir başka uygulanabilir (değiştirilebilir giriş yineleyici) özel nesne türünün sıralanması X, özellikle aşağıdaki gibi standart kütüphane algoritmalarının kullanılması dahil olmak üzere çeşitli yöntemler kullanılarak elde edilebilir.

Tekniklerin çoğu beri, göreli sipariş elde etmek X öğelerin için zaten gönderildiğinden, çeşitli yaklaşımları kullanmak için "neden" ve "ne zaman" ile ilgili bazı notlar ile başlayacağım.

"En iyi" yaklaşım farklı faktörlere bağlı olacaktır:

  1. XNesnelerin aralıklarını sıralamak yaygın veya nadir bir görev midir (bu aralıklar programda veya kütüphane kullanıcıları tarafından farklı yerlerde sıralanacaktır)?
  2. Gerekli sıralama "doğal" mı (beklenen) veya türün kendisiyle karşılaştırılabilecek birden fazla yolu var mı?
  3. Performans bir sorun mudur yoksa Xnesne sıralaması kusursuz mu olmalıdır?

Sıralama aralıkları Xortak bir görevse ve elde edilen sıralama beklenirse (yani Xyalnızca tek bir temel değeri sararsa), muhtemelen aşırı yüklenmeye gideroperator< sararsa), herhangi bir fuzz olmadan (uygun karşılaştırıcıları doğru bir şekilde geçirme gibi) sıralama ve tekrarlanan beklenen verim sağladığı devam eder. Sonuçlar.

Sıralama ortak bir görevse veya farklı bağlamlarda gerekli olması gerekiyorsa, ancak Xnesneleri sıralamak için kullanılabilecek birden fazla ölçüt varsa, Functors ( operator()özel sınıfların aşırı yüklenmiş işlevleri) veya işlev işaretçileri (yani bir functor / işlevi) sözcüksel sıralama için ve diğeri doğal sıralama için).

Tür sıralama aralıkları Xnadir veya diğer bağlamlarda olası değilse , daha fazla işlev veya türle herhangi bir ad alanını karıştırmak yerine lambdaları kullanma eğilimindeyim.

Bu, sıralama bir şekilde "açık" veya "doğal" değilse özellikle doğrudur. Yerinde uygulanan bir operator<lambdaya bakarken mantığı siparişin arkasına kolayca alabilirsiniz, oysa ilk bakışta opague ve hangi sipariş mantığının uygulanacağını bilmek için tanımına bakmanız gerekir.

Bununla birlikte, tek bir operator<tanımın tek bir hata noktası olduğunu, ancak birden çok lambanın birden çok hata noktası olduğunu ve daha fazla dikkat gerektirdiğini unutmayın.

Eğer tanımı operator< sıralama sıralama şablon derlenmiş / yapıldığı yerle ilgili kullanılamaz, derleyici nesneleri karşılaştırarak, yerine ne zaman en azından ciddi bir dezavantaj olabilir sipariş mantığı (inlining zaman bir işlev çağrısı yapmak zorunda kalabileceğini bağlantı zamanı optimizasyonu / kod oluşturma uygulanmaz).

class XStandart kütüphane sıralama algoritmalarını kullanarak karşılaştırılabilirlik elde etmenin yolları

Bırakın std::vector<X> vec_X;vestd::vector<Y> vec_Y;

1. Karşılaştırma işlevi beklemeyen standart kitaplık şablonlarını aşırı yükleyin T::operator<(T)veya operator<(T, T)kullanın.

Her iki aşırı yük üyesi operator<:

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

veya ücretsiz 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ıralama işlevi parametresi olarak özel bir karşılaştırma işlevine sahip bir işlev işaretçisi kullanın.

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)Karşılaştırma işlevi olarak geçirilebilen özel bir tür için bir aşırı yük oluşturun .

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

Bu işlev nesnesi tanımları C ++ 11 ve şablonlar kullanılarak biraz daha genel olarak yazılabilir:

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

üye idestekli herhangi bir türü sıralamak için kullanılabilir <.

4. Sıralama işlevlerine karşılaştırma parametresi olarak bir anonim kapanışı (lambda) geçirin.

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 daha da genel bir lambda ifadesini mümkün kılıyorsa:

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

bir makroya sarılabilen

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

sıradan karşılaştırıcı oluşturma işlemini oldukça pürüzsüz hale getirir:

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

In 2. durumda yazdığın bool X_less(X const &l, X const &r) const { return l.i < r.i; }emsal kişiye ancak const(bir üye işlev değil gibi) anahtar kelimeler çıkarılmalıdır.
PolGraphic

@PolGraphic: Doğru - durum 1'de de.
Pixelchemist

@Pixelchemist (4.) lambda yaklaşımını kullanmadığımda std::sortveya benzeri olmadığında nasıl kullanacağım , ancak örneğin bir örneğini Comparebaşlatırken bir örneğine ihtiyaç duymalıyım std::set?
azrdev

1
@azrdev: Ayarlanacak şablon parametresi olarak template<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }iletilecek kapağın türünü yakalayan bir işlev şablonu: gibi kullanılabilir auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });.
Pixelchemist

14

Doğru yoldasın. varsayılan olarak karşılaştırma işlevi olarak std::sortkullanılır operator<. Bu nedenle, nesnelerinizi sıralamak için, aşırı yüklemeyi yapmanız bool operator<( const T&, const T& )veya karşılaştırmayı yapan bir işlev sağlamanız gerekir.

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

Bir functor kullanmanın avantajı, sınıfın özel üyelerine erişimi olan bir işlevi kullanabilmenizdir.


Bunu kaçırdı: bir üye işlev operatörü sağla
xtofl

1
Bir operator<sınıf (veya yapı) üyesi yapmak daha iyidir , çünkü küresel olan korunan veya özel üyeleri kullanabilir. Veya Struct C'nin bir arkadaşı olmalısınız
Kirill V. Lyadvinsky

5

Ben std :: sort çağırabilirsiniz çeşitli yollar arasında performans üzerinde herhangi bir ölçülebilir etkisi olup olmadığını merak ettim, bu yüzden bu basit testi oluşturdum:

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

Yaptığı şey rastgele bir vektör oluşturur ve daha sonra kopyalamak ve kopyasını sıralamak için ne kadar zaman gerektiğini ölçer (ve çok kuvvetli ölü kodların ortadan kaldırılmasını önlemek için bazı sağlama toplamı hesaplar).

G ++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1) ile derliyordum

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

İşte sonuçlar:

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

Görünüşe göre işlev işaretçisi geçme dışındaki tüm seçenekler çok benzer ve işlev işaretçisi geçirme +% 30 cezaya neden olur.

Ayrıca, operatör <sürümü ~% 1 daha yavaş (testi birden çok kez tekrarladım ve etki devam ediyor) gibi görünüyor, bu da üretilen kodun farklı olduğunu düşündüren biraz garip (analiz etme yeteneğim yok - kaydetme- sıcaklık çıkışı).



3

Sınıfınızda "<" işlecini aşırı yükleyebilirsiniz.

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

3

Aşağıda lambdas kullanan kod

#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

Kullanıcı tanımlı karşılaştırıcı sınıfını kullanabilirsiniz.

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

0

Bir vektörü sıralamak için içindeki sort () algoritmasını kullanabilirsiniz.

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

Kullanılan üçüncü parametre daha büyük veya daha az olabilir veya herhangi bir işlev veya nesne de kullanılabilir. Ancak üçüncü parametreyi boş bırakırsanız varsayılan operatör <olur.

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

karşılaştırma yanlışsa, "takas" yapacaktır.


Bu hiçbir dilde derlenmeyecektir.
LF
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.