82

I would like to sort a vector

vector<myClass> object;

Where myclass contains many int variables. How can I sort my vector on any specific data variable of myClass.

Gal Dreiman
  • 3,969
  • 2
  • 21
  • 40
NativeCoder
  • 1,033
  • 1
  • 10
  • 16

5 Answers5

123
std::sort(object.begin(), object.end(), pred());

where, pred() is a function object defining the order on objects of myclass. Alternatively, you can define myclass::operator<.

For example, you can pass a lambda:

std::sort(object.begin(), object.end(),
          [] (myclass const& a, myclass const& b) { return a.v < b.v; });

Or if you're stuck with C++03, the function object approach (v is the member on which you want to sort):

struct pred {
    bool operator()(myclass const & a, myclass const & b) const {
        return a.v < b.v;
    }
};
avakar
  • 32,009
  • 9
  • 68
  • 103
  • @NativeCoder that's what the operator is for - you can define it however you like and according to whatever variable you want. it's called Operator Overloading http://www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html. – Amir Rachum May 03 '10 at 12:54
  • 8
    The predicate approach is quite better than the operator overloading approach if you don't have a generic ordering of this particular class but just want to sort it for this vector. – Matthieu M. May 03 '10 at 13:01
80

Overload less than operator, then sort. This is an example I found off the web...

class MyData
{
public:
  int m_iData;
  string m_strSomeOtherData;
  bool operator<(const MyData &rhs) const { return m_iData < rhs.m_iData; }
};

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

Source: here

Gabe
  • 49,577
  • 28
  • 142
  • 181
  • 14
    You will want to make op<() const, and to pass its parameter as a const reference. –  May 03 '10 at 13:13
  • 19
    @Neil, I posted the example I found because I didn't have time to type it all dude. IT was a solid example and solved the problem. I am glad it took you 40 mins to decide down-vote it. I could see it being down-voted if I didn't include the source site, but I did. It's not like I tried to pawn it off as my own. – Gabe May 03 '10 at 13:33
  • 9
    @Neil I will admit it has been a while since I have used c++, but i remembered some general ideas with this question that's why I answered. I am not claiming it is perfect, but it does work, i tried it myself. I took your suggestion and added it. If you have some other problem with it, speak up instead acting so condescending. Acting like that is not was SO is about either dude. – Gabe May 03 '10 at 13:39
  • 1
    @gmcalab: Your edit did not make `operator<` const. It made the return value const, which doesn't make much difference since it is a `bool` returned by value. As it is, you cannot use this comparison in situations where the left-hand operand is const. – Fred Larson May 03 '10 at 13:42
  • 4
    If you tried it and it "works" your compiler is broken. And please don't call me "dude". –  May 03 '10 at 13:44
  • 2
    @gmcalab Your misunderstanding of what Neil meant by "make op<() const" demonstrates that you don't really understand the code you posted. Just because something "works" for you doesn't mean that it is the correct thing to do. – Tyler McHenry May 03 '10 at 13:44
  • @Neil, it will work in this particular case, because elements of `myvector` are certainly non-const (otherwise you couldn't sort the vector). – avakar May 03 '10 at 13:48
  • @avakar Not with g++4.4.1. Just because the things being sorted are not const does not mean it is ok to apply non-const functions to them - or do you think sort can change the elements it sorts? –  May 03 '10 at 13:50
  • @Neil, `sort` certainly can change elements of the sequence it sorts -- it wouldn't be able to sort it otherwise. What I missed, however, is that `std::less::operator()` naturally takes its parameters by const reference, so you're right, the code was incorrect. – avakar May 03 '10 at 14:52
  • @avakar I guess I don't think of swapping as changing the elements :-) –  May 03 '10 at 15:08
  • Don't forget to `#include ` – Tomasz Wiśniewski May 17 '18 at 09:01
15

A pointer-to-member allows you to write a single comparator, which can work with any data member of your class:

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

template <typename T, typename U>
struct CompareByMember {
    // This is a pointer-to-member, it represents a member of class T
    // The data member has type U
    U T::*field;
    CompareByMember(U T::*f) : field(f) {}
    bool operator()(const T &lhs, const T &rhs) {
        return lhs.*field < rhs.*field;
    }
};

struct Test {
    int a;
    int b;
    std::string c;
    Test(int a, int b, std::string c) : a(a), b(b), c(c) {}
};

// for convenience, this just lets us print out a Test object
std::ostream &operator<<(std::ostream &o, const Test &t) {
    return o << t.c;
}

int main() {
    std::vector<Test> vec;
    vec.push_back(Test(1, 10, "y"));
    vec.push_back(Test(2, 9, "x"));

    // sort on the string field
    std::sort(vec.begin(), vec.end(), 
        CompareByMember<Test,std::string>(&Test::c));
    std::cout << "sorted by string field, c: ";
    std::cout << vec[0] << " " << vec[1] << "\n";

    // sort on the first integer field
    std::sort(vec.begin(), vec.end(), 
        CompareByMember<Test,int>(&Test::a));
    std::cout << "sorted by integer field, a: ";
    std::cout << vec[0] << " " << vec[1] << "\n";

    // sort on the second integer field
    std::sort(vec.begin(), vec.end(), 
        CompareByMember<Test,int>(&Test::b));
    std::cout << "sorted by integer field, b: ";
    std::cout << vec[0] << " " << vec[1] << "\n";
}

Output:

sorted by string field, c: x y
sorted by integer field, a: y x
sorted by integer field, b: x y
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Hi Steve, I've been thinking about solving the same problem as this question without much progress! Your solution looks very good to me. I think it would have taken me a long time to come up with it, if ever! I've read Myers' Effective C++ & Effective STL and Dewhurst's C++ Common Knowledge. I wonder if you could recommend some more reading, do you know of any books that cover examples like your one above in particular or failing that any more general suggestions to help me see solutions like that? – Paul Caheny Nov 23 '10 at 23:46
  • 1
    @Czarak: looking at it again, it could probably be improved. For example, it might optimize better if the pointer to member was a template parameter: `template struct CompareByMember2 { bool operator()(const T &lhs, const T & rhs) { return lhs.*F < rhs.*F; }};`. Whether that's possible depends whether the caller is using a variable for the member to sort by, or different callers specify different specific members. – Steve Jessop Nov 23 '10 at 23:48
  • @Czarak: as for reading, I'm not sure. The "trick" here is the combination of member pointers with templates, I think it's a matter of being comfortable writing templates, and knowing what can be done with them. Vandevoorde & Josuttis' book "C++ Templates - The Complete Guide" is supposed to be good, but I haven't read it. It might be old enough and expensive enough that there's a better option by now. Look at http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list. And note that if you have C++0x, a lambda function may beat writing out the whole class for this! – Steve Jessop Nov 24 '10 at 00:01
  • Hi Steve, Thanks for the very helpful answers. I've been looking into lambda functions as you suggested, it led me to another problem, one which I believe I would also have if I was to go with a solution along the lines of what you suggested in your answer above. If you can spare the time maybe you could take a look? http://stackoverflow.com/questions/4268848/c-lambdas-for-stdsort-and-stdlower-bound-equal-range-on-a-struct-element-in – Paul Caheny Nov 24 '10 at 16:40
  • I found out that when you provide a function that creates an instance of the templated object the calling function can be reduced to this "sort(begin(vec), end(vec), by_member(&Test::c) );" – Arne Jun 22 '14 at 18:29
9

Like explained in other answers you need to provide a comparison function. If you would like to keep the definition of that function close to the sort call (e.g. if it only makes sense for this sort) you can define it right there with boost::lambda. Use boost::lambda::bind to call the member function.

To e.g. sort by member variable or function data1:

#include <algorithm>
#include <vector>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>
using boost::lambda::bind;
using boost::lambda::_1;
using boost::lambda::_2;

std::vector<myclass> object(10000);
std::sort(object.begin(), object.end(),
    bind(&myclass::data1, _1) < bind(&myclass::data1, _2));
Benjamin Bannier
  • 55,163
  • 11
  • 60
  • 80
2

this is my approach to solve this generally. It extends the answer from Steve Jessop by removing the requirement to set template arguments explicitly and adding the option to also use functoins and pointers to methods (getters)

#include <vector>
#include <iostream>
#include <algorithm>
#include <string>
#include <functional>

using namespace std;

template <typename T, typename U>
struct CompareByGetter {
    U (T::*getter)() const;
    CompareByGetter(U (T::*getter)() const) : getter(getter) {};
    bool operator()(const T &lhs, const T &rhs) {
        (lhs.*getter)() < (rhs.*getter)();
    }
};

template <typename T, typename U>
CompareByGetter<T,U> by(U (T::*getter)() const) {
    return CompareByGetter<T,U>(getter);
}

//// sort_by
template <typename T, typename U>
struct CompareByMember {
    U T::*field;
    CompareByMember(U T::*f) : field(f) {}
    bool operator()(const T &lhs, const T &rhs) {
        return lhs.*field < rhs.*field;
    }
};

template <typename T, typename U>
CompareByMember<T,U> by(U T::*f) {
    return CompareByMember<T,U>(f);
}

template <typename T, typename U>
struct CompareByFunction {
    function<U(T)> f;
    CompareByFunction(function<U(T)> f) : f(f) {}
    bool operator()(const T& a, const T& b) const {
        return f(a) < f(b);
    }
};

template <typename T, typename U>
CompareByFunction<T,U> by(function<U(T)> f) {
    CompareByFunction<T,U> cmp{f};
    return cmp;
}

struct mystruct {
    double x,y,z;
    string name;
    double length() const {
        return sqrt( x*x + y*y + z*z );
    }
};

ostream& operator<< (ostream& os, const mystruct& ms) {
    return os << "{ " << ms.x << ", " << ms.y << ", " << ms.z << ", " << ms.name << " len: " << ms.length() << "}";
}

template <class T>
ostream& operator<< (ostream& os, std::vector<T> v) {
    os << "[";
    for (auto it = begin(v); it != end(v); ++it) {
        if ( it != begin(v) ) {
            os << " ";
        }
        os << *it;
    }
    os << "]";
    return os;
}

void sorting() {
    vector<mystruct> vec1 = { {1,1,0,"a"}, {0,1,2,"b"}, {-1,-5,0,"c"}, {0,0,0,"d"} };

    function<string(const mystruct&)> f = [](const mystruct& v){return v.name;};

    cout << "unsorted  " << vec1 << endl;
    sort(begin(vec1), end(vec1), by(&mystruct::x) );
    cout << "sort_by x " << vec1 << endl;
    sort(begin(vec1), end(vec1), by(&mystruct::length));
    cout << "sort_by len " << vec1 << endl;
    sort(begin(vec1), end(vec1), by(f) );
    cout << "sort_by name " << vec1 << endl;
}
Arne
  • 7,921
  • 9
  • 48
  • 66