Comment fait-on pour trier un vecteur contenant des objets personnalisés (définis par l’utilisateur).
Probablement, l’algorithme STL standard sort avec un prédicat (une fonction ou un objet fonction) qui agirait sur l’un des champs (en tant que clé de tri) de l’objet personnalisé devrait être utilisé.
Suis-je sur la bonne voie?
Un exemple simple utilisant 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());
_
Edit: Comme Kirill V. Lyadvinsky l'a fait remarquer, au lieu de fournir un prédicat de tri, vous pouvez implémenter le _operator<
_ pour 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);
}
};
_
Utiliser cette méthode signifie que vous pouvez simplement trier le vecteur comme suit:
_std::sort(vec.begin(), vec.end());
_
Edit2: Comme le suggère Kappa, vous pouvez également trier le vecteur dans l'ordre décroissant en surchargeant un opérateur _>
_ et en modifiant l'appel de sorte à trier un bit:
_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);
}
};
_
Et vous devriez appeler trier comme:
_std::sort(vec.begin(), vec.end(),greater<MyStruct>());
_
Dans l'intérêt de la couverture. Je propose une implémentation en utilisant expressions 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;
});
Vous pouvez utiliser functor comme troisième argument de std::sort
ou définir operator<
dans votre 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() );
}
Tu es sur la bonne piste. std::sort
utilisera operator<
comme fonction de comparaison par défaut. Donc, pour trier vos objets, vous devrez soit surcharger bool operator<( const T&, const T& )
, soit fournir un foncteur qui fera la comparaison, à peu près comme ceci:
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 );
L’utilisation d’un foncteur présente l’avantage de pouvoir utiliser une fonction permettant d’accéder aux membres privés de la classe.
Le tri d'une telle gamme vector
ou de toute autre plage applicable (itérateur d'entrée mutable) d'objets personnalisés de type X
peut être réalisé à l'aide de différentes méthodes, notamment l'utilisation d'algorithmes de bibliothèque standard, tels que
Puisque la plupart des techniques, pour obtenir un ordre relatif des éléments X
, ont déjà été postées, je commencerai par quelques notes sur le "pourquoi" et le "quand" pour utiliser les différentes approches.
La "meilleure" approche dépendra de différents facteurs:
X
est-il une tâche courante ou rare (ces plages seront-elles triées en plusieurs endroits du programme ou par les utilisateurs de la bibliothèque)?X
doivent-elles être infaillibles?Si le tri des plages de X
est une tâche courante et que le tri obtenu est à prévoir (c’est-à-dire que X
encapsule une valeur fondamentale unique), la surcharge sera probablement activée _operator<
_ car elle permet un tri sans fuzz (comme passer correctement) des comparateurs appropriés) et donne à plusieurs reprises les résultats escomptés.
Si le tri est une tâche courante ou susceptible d'être requise dans différents contextes, mais que plusieurs critères peuvent être utilisés pour trier les objets X
, nous préférerions les Functors (fonctions surchargées operator()
des classes personnalisées) ou les pointeurs de fonction. (c.-à-d. un foncteur/fonction pour la commande lexicale et un autre pour la commande naturelle).
Si le tri des plages de type X
est rare ou improbable dans d'autres contextes, j'ai tendance à utiliser lambdas au lieu d'encombrer les espaces de noms contenant davantage de fonctions ou de types.
Cela est particulièrement vrai si le tri n'est pas "clair" ou "naturel" d'une manière ou d'une autre. Vous pouvez facilement comprendre la logique derrière la commande quand vous regardez un lambda qui est appliqué sur place alors que _operator<
_ est opaque à première vue et il vous faudrait regarder la définition pour savoir quelle logique de commande sera appliquée.
Notez cependant qu'une seule définition _operator<
_ est un point d'échec unique, tandis que plusieurs lambas sont des points d'échec multiples et nécessitent une plus grande prudence.
Si la définition de _operator<
_ n'est pas disponible à l'endroit où le tri est effectué/le modèle de tri est compilé, le compilateur peut être contraint de passer un appel de fonction lors de la comparaison d'objets, au lieu d'inclure la logique de classement, qui peut être une tâche grave. inconvénient (du moins lorsque l'optimisation du temps de liaison/la génération de code n'est pas appliquée).
class X
_ afin d'utiliser des algorithmes de tri de bibliothèque standard Soit _std::vector<X> vec_X;
_ et _std::vector<Y> vec_Y;
_
T::operator<(T)
ou operator<(T, T)
et utilisez des modèles de bibliothèque standard qui n'attendent pas de fonction de comparaison.Soit le membre de surcharge _operator<
_:
_struct X {
int i{};
bool operator<(X const &r) const { return i < r.i; }
};
// ...
std::sort(vec_X.begin(), vec_X.end());
_
ou gratuit _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());
_
_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);
_
bool operator()(T, T)
pour un type personnalisé pouvant être passée en tant que foncteur de comparaison._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{});
_
Ces définitions d'objet de fonction peuvent être écrites un peu plus génériques à l'aide de C++ 11 et de modèles:
_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; }
};
_
qui peut être utilisé pour trier n'importe quel type avec le membre i
supportant _<
_.
_struct X {
int i{}, j{};
};
std::sort(vec_X.begin(), vec_X.end(), [](X const &l, X const &r) { return l.i < r.i; });
_
Où C++ 14 permet une expression lambda encore plus générique:
_std::sort(a.begin(), a.end(), [](auto && l, auto && r) { return l.i < r.i; });
_
qui pourrait être enveloppé dans une macro
_#define COMPARATOR(code) [](auto && l, auto && r) -> bool { return code ; }
_
rendant la création de comparateur ordinaire assez lisse:
_// 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));
_
Oui, std::sort()
avec un troisième paramètre (fonction ou objet) serait plus facile. Un exemple: http://www.cplusplus.com/reference/algorithm/sort/
Dans votre classe, vous pouvez surcharger l'opérateur "<".
class MyClass
{
bool operator <(const MyClass& rhs)
{
return this->key < rhs.key;
}
}
J'étais curieux de savoir s'il y avait un impact mesurable sur les performances entre les différentes manières d'appeler std :: sort. J'ai donc créé ce test simple:
$ 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;
}
Cela crée un vecteur aléatoire, puis mesure le temps requis pour le copier et le trier (et calculer une somme de contrôle pour éviter une élimination trop vigoureuse du code mort).
Je compilais avec g ++ (GCC) 7.2.1 20170829 (Red Hat 7.2.1-1)
$ g++ -O2 -o sort sort.cpp && ./sort
Voici les résultats:
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
On dirait que toutes les options, à l'exception du passage du pointeur de fonction, sont très similaires, et le passage d'un pointeur de fonction provoque une pénalité de + 30%.
Il semble également que l'opérateur <version est environ 1% plus lent (j'ai répété le test plusieurs fois et l'effet persiste), ce qui est un peu étrange car cela suggère que le code généré est différent (je manque de compétences pour analyser - enregistrer - temps de sortie).
Ci-dessous le code utilisant 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;
}
Vous pouvez utiliser la classe de comparaison définie par l'utilisateur.
class comparator
{
int x;
bool operator()( const comparator &m, const comparator &n )
{
return m.x<n.x;
}
}
// 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;
}
Pour trier un vecteur, vous pouvez utiliser l’algorithme sort () dans.
sort(vec.begin(),vec.end(),less<int>());
Le troisième paramètre utilisé peut être supérieur ou inférieur ou toute fonction ou objet peut également être utilisé. Cependant, l'opérateur par défaut est <si vous laissez le troisième paramètre vide.
// 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);
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);
}
si compare est faux, il fera "swap".