Ordinamento di un vettore di oggetti personalizzati


249

Come si fa a ordinare un vettore contenente oggetti personalizzati (cioè definiti dall'utente).
Probabilmente standard algoritmo STL ordinamento deve essere usato insieme con un predicato (una funzione o un oggetto funzione) che operano su uno dei campi (come chiave per l'ordinamento) nell'oggetto personalizzato.
Sono sulla buona strada?


Possibile duplicato dell'ordinamento della libreria standard e dei tipi definiti dall'utente
MCCCS

Risposte:


365

Un semplice esempio usando 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());

Modifica: come ha sottolineato Kirill V. Lyadvinsky, invece di fornire un predicato di ordinamento, è possibile implementare il operator<per 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);
    }
};

L'uso di questo metodo significa che puoi semplicemente ordinare il vettore come segue:

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

Edit2: Come suggerisce Kappa, puoi anche ordinare il vettore in ordine decrescente sovraccaricando un >operatore e cambiando un po 'la chiamata di ordinamento:

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

E dovresti chiamare sort come:

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

2
Potresti spiegare perché hai creato la funzione di confronto nell'esempio struct less_than_key (nel primo) in linea?
kluka,

2
e un'altra domanda / nota: se si desidera avere più metodi di ordinamento (per attributi diversi) in una classe, il modo di sovraccaricare l'operatore <non è probabilmente un'opzione, giusto?
kluka,

5
Una cosa interessante è fornire anche il metodo operatore>. Questo ci permetterà di ordinare in ordine inverso come:, std::sort(vec.begin(), vec.end(), greater<MyStruct>())che è pulito ed elegante.
kappa,

3
@Bovaz Devi #include <functional>usare "std :: greater".
Nick Hartung,

4
@kappa: dove potresti semplicemente avere operator<e usare uno std::sort(vec.begin(), vec.end());o std::sort(vec.rbegin(), vec.rend());secondo se vuoi avere un ordine crescente o decrescente.
Pixelchemist,

182

Nell'interesse della copertura. Ho presentato un'implementazione usando espressioni 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
+1 extra per l'inclusione di #includes
Anne

3
Per essere chiari, questo si traduce in ordine crescente; utilizzare >invece di <ottenere l'ordine decrescente.
Bhaller,

57

Puoi usare functor come terzo argomento std::sorto potresti definire operator<nella tua classe.

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
perché dobbiamo aggiungere constalla fine della firma della funzione?
punte

4
La funzione non cambia l'oggetto così com'è const.
Kirill V. Lyadvinsky il

Se questo è il caso, allora perché passiamo "const X & val", presumo che passare il valore come const a una funzione faccia pensare alla funzione che il suo valore non cambierà.
Prashant Bhanarkar,

1
@PrashantBhanarkar La constparola chiave alla fine della firma specifica che la operator()funzione non modifica l'istanza della Xgreaterstruttura (che in generale potrebbe avere variabili membro), mentre l'indicazione constper i valori di input specifica solo che tali valori di input sono immutabili.
schester,

15

L'ordinamento di tale vectoro qualsiasi altro intervallo di oggetti personalizzati di tipo (mutable input iterator) Xpuò essere ottenuto utilizzando vari metodi, in particolare l'uso di algoritmi di libreria standard come

Poiché la maggior parte delle tecniche, per ottenere l'ordinamento relativo degli Xelementi, sono già state pubblicate, inizierò con alcune note su "perché" e "quando" per utilizzare i vari approcci.

L'approccio "migliore" dipenderà da diversi fattori:

  1. L'ordinamento di intervalli di Xoggetti è un'attività comune o rara (tali intervalli verranno ordinati in più punti diversi nel programma o dagli utenti della biblioteca)?
  2. L'ordinamento richiesto è "naturale" (previsto) o esistono diversi modi in cui il tipo può essere confrontato con se stesso?
  3. Le prestazioni sono un problema o gli intervalli di classificazione degli Xoggetti devono essere infallibili?

Se l'ordinamento di intervalli di Xè un compito comune e ci si aspetta l'ordinamento raggiunto (ovvero Xavvolge solo un singolo valore fondamentale), allora probabilmente andrebbe in sovraccarico operator<poiché consente l'ordinamento senza alcun fuzz (come il corretto passaggio di comparatori adeguati) e più volte i rendimenti previsti risultati.

Se l'ordinamento è un'attività comune o è probabile che sia richiesta in contesti diversi, ma ci sono più criteri che possono essere utilizzati per ordinare gli Xoggetti, sceglierei Functors ( operator()funzioni sovraccariche di classi personalizzate) o puntatori a funzioni (ovvero un funzione / funzione per l'ordinamento lessicale e un altro per l'ordinamento naturale).

Se l'ordinamento di intervalli di tipo Xè raro o improbabile in altri contesti, tendo a usare lambdas invece di ingombrare qualsiasi spazio dei nomi con più funzioni o tipi.

Ciò è particolarmente vero se l'ordinamento non è "chiaro" o "naturale" in qualche modo. Puoi facilmente ottenere la logica dietro l'ordinamento quando guardi una lambda che viene applicata sul posto mentre operator<è opaca a prima vista e dovresti cercare la definizione per sapere quale logica di ordinamento verrà applicata.

Si noti tuttavia che una singola operator<definizione è un singolo punto di errore mentre più lambas sono più punti di errore e richiedono una maggiore attenzione.

Se la definizione di operator<non è disponibile dove viene eseguito l'ordinamento / viene compilato il modello di ordinamento, il compilatore potrebbe essere costretto a effettuare una chiamata di funzione durante il confronto di oggetti, invece di allineare la logica di ordinamento che potrebbe essere un grave svantaggio (almeno quando non viene applicata l'ottimizzazione del tempo di collegamento / generazione del codice).

Modi per ottenere la comparabilità di class Xal fine di utilizzare algoritmi di ordinamento delle librerie standard

Let std::vector<X> vec_X;andstd::vector<Y> vec_Y;

1. Sovraccaricare T::operator<(T)o operator<(T, T)utilizzare modelli di libreria standard che non prevedono una funzione di confronto.

O elemento di sovraccarico operator<:

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

o gratis 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. Utilizzare un puntatore a funzione con una funzione di confronto personalizzata come parametro della funzione di ordinamento.

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. Creare un bool operator()(T, T)sovraccarico per un tipo personalizzato che può essere passato come funzione di confronto.

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

Queste definizioni di oggetti funzione possono essere scritte un po 'più generiche usando C ++ 11 e modelli:

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

che può essere utilizzato per ordinare qualsiasi tipo con isupporto membro <.

4. Passare una chiusura anonima (lambda) come parametro di confronto alle funzioni di ordinamento.

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

Dove C ++ 14 abilita un'espressione lambda ancora più generica:

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

che potrebbe essere racchiuso in una macro

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

rendendo abbastanza ordinaria la creazione di comparatori:

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

Nel 2. caso hai scritto bool X_less(X const &l, X const &r) const { return l.i < r.i; }per il comparatore, ma le constparole chiave dovrebbero essere rimosse (in quanto non è una funzione membro).
PolGraphic,

@PolGraphic: Corretto - anche nel caso 1.
Pixelchemist,

@Pixelchemist come dovrei usare l'approccio (4.) lambda quando non lo uso std::sorto simile, ma avevo bisogno di un'istanza di Compare, ad esempio quando si crea un'istanza di std::set?
Azrdev,

1
@azrdev: un modello di funzione che acquisisce il tipo di chiusura per passarlo come parametro modello da impostare: template<class T, class C> std::set<T, C> make_set(C const& compare) { return std::set<T, C>{ compare }; }che potrebbe essere usato come auto xset = make_set<X>([](auto && l, auto && r) { return l.i < r.i; });.
Pixelchemist

14

Sei sulla strada giusta. std::sortutilizzerà operator<come funzione di confronto per impostazione predefinita. Quindi, per ordinare i tuoi oggetti, dovrai sovraccaricare bool operator<( const T&, const T& )o fornire un funzione che faccia il confronto, in questo modo:

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

Il vantaggio dell'uso di un funzione è che puoi usare una funzione con accesso ai membri privati ​​della classe.


Perso quello: fornire un operatore funzione membro <.
xtofl,

1
È meglio fare operator<un membro di classe (o struttura), perché uno globale potrebbe usare membri protetti o privati. O dovresti renderlo amico di Struct C.
Kirill V. Lyadvinsky,

5

Ero curioso di sapere se c'è un impatto misurabile sulle prestazioni tra i vari modi in cui si può chiamare std :: sort, quindi ho creato questo semplice test:

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

Ciò che fa è creare un vettore casuale, quindi misurare quanto tempo è necessario per copiarlo e ordinare la copia di esso (e calcolare un checksum per evitare un'eliminazione del codice morto troppo vigorosa).

Stavo compilando con g ++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)

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

Ecco i risultati:

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

Sembra che tutte le opzioni ad eccezione del passaggio del puntatore a funzione siano molto simili e passare un puntatore a funzione provoca una penalità del + 30%.

Sembra anche che l'operatore <versione sia ~ 1% più lenta (ho ripetuto il test più volte e l'effetto persiste), il che è un po 'strano poiché suggerisce che il codice generato è diverso (non ho abilità da analizzare --save- temps output).



3

Nella tua classe, puoi sovraccaricare l'operatore "<".

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

3

Di seguito è riportato il codice che utilizza 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

È possibile utilizzare una classe di confronto definita dall'utente.

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

0

Per ordinare un vettore puoi usare l'algoritmo sort () in.

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

Il terzo parametro utilizzato può essere maggiore o minore oppure è possibile utilizzare anche qualsiasi funzione o oggetto. Tuttavia, l'operatore predefinito è <se si lascia vuoto il terzo parametro.

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

se compare è falso, farà "scambio".


In nessuna lingua questa compilazione.
LF
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.