21

I have a vector of tuples like

vector<tuple<T1, T2, T3>> v;

I believe that when the default comparison kicks in for tuple types, it performs a lexicographical comparison.

Can I perform the comparisons by the element I choose ? Eg by the second element in the above example or by the ith element in a tuple containing m types ?

Thank you in advance.

4 Answers4

25

There are many ways to do that, the one I use boils down to declaring a custom comparison object, actually the following

// Functor to compare by the Mth element
template<int M, template<typename> class F = std::less>
struct TupleCompare
{
    template<typename T>
    bool operator()(T const &t1, T const &t2)
    {
        return F<typename tuple_element<M, T>::type>()(std::get<M>(t1), std::get<M>(t2));
    }
};

It works for tuples of arbitrary length (avoiding variadic templates - even though it's fairly easy and safer to do it the variadic way because you can declare the arguments of operator() as tuples of arbitrary length) and also works for pairs and you can pass it a custom comparison function/object but uses < operator as the default policy. An example usage would be

int main()
{
    vector<tuple<int, string>> v;
    v.push_back(make_tuple(1, "Hello"));
    v.push_back(make_tuple(2, "Aha"));

    std::sort(begin(v), end(v), TupleCompare<0>());
    return 0;
}

there is ofcourse a more modern way, by using lambdas, so the sorting line would be

std::sort(begin(v), end(v), 
    [](tuple<int, string> const &t1, tuple<int, string> const &t2) {
        return get<0>(t1) < get<0>(t2); // or use a custom compare function
    }
);

I believe it's worth making a function object for this (avoids a lot of boilerplate code) and go with the first approach


EDIT

As Yakk's comment (on c++1y) became standard compliant (c++14), we demonstrate below the case for generic lambdas

std::sort(begin(v), end(v), [](auto const &t1, auto const &t2) {
        return get<0>(t1) < get<0>(t2); // or use a custom compare function
});

which greatly matches the mechanics of TupleCompare since the operator() is templated as well.

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
12

You can do it like this

#include <tuple>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

vector<tuple<int, float, char>> v;

int main() {
  v.push_back(std::make_tuple(1,1.2,'c'));
  v.push_back(std::make_tuple(1,1.9,'e'));
  v.push_back(std::make_tuple(1,1.7,'d'));

  sort(v.begin(),v.end(),
       [](const tuple<int,float,char>& a,
       const tuple<int,float,char>& b) -> bool
       {
         return std::get<1>(a) > std::get<1>(b);
       });

  cout << std::get<2>(v[0]) << endl;
  return 0;
}
Jens Munk
  • 4,627
  • 1
  • 25
  • 40
4

Yes, you can define custom sorting in C++ when you need it. I assume you need it for std::sort, right? Look at the documentation of std::sort, at the second version of the algorithm to be precise, the one which takes the comp argument.

You have to define a less-than functor, something like this:

struct CustomLessThan
{
    bool operator()(tuple<T1, T2, T3> const &lhs, tuple<T1, T2, T3> const &rhs) const
    {
        return std::get<1>(lhs) < std::get<1>(rhs);
    }
};

And then use it in std::sort:

std::sort(v.begin(), v.end(), CustomLessThan());

In C++11, you can make the code much shorter by using a lambda instead of creating a named struct. The examples given at cppreference.com also show this technique.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • @AlexanderMills: Which of the three `const` are you referring to? The one for the operator itself? That makes the function `const` and is just good practice here. – Christian Hackl Jan 07 '17 at 11:36
  • oh haha I didn't know you could do that with the 3rd const – Alexander Mills Jan 07 '17 at 11:37
  • @AlexanderMills: A good rule of thumb is to make everything `const` by default, be it variables or functions. Allow modification only where it's really needed. This is true for every language which has a similar concept. Immutabilty makes code easier to understand and much more stable. For example, in Java it's good practice to make everything `final` until/unless modification is required. Unfortunately, Java does not have `final` methods, though. – Christian Hackl Jan 07 '17 at 11:46
  • I am pretty certain java has final methods, it's just when you declare a class final – Alexander Mills Jan 07 '17 at 11:48
  • oh nevermind, you can create final methods - https://docs.oracle.com/javase/tutorial/java/IandI/final.html – Alexander Mills Jan 07 '17 at 11:49
  • @AlexanderMills: I expressed myself badly. Java does have `final` methods in the sense that you cannot override them anymore. C++(11) has those, too. What Java lacks is an equivalent to C++ `const` member functions. There is no way to tell a Java method that it must not modify any field. – Christian Hackl Jan 07 '17 at 11:50
  • I see what you mean, but I think you can do that in Java like this http://stackoverflow.com/questions/12792260/how-to-declare-a-constant-in-java...I believe that "freezes" a variable, but not 100% certain. I am very familiar with Java and Node.js - I am working on C++ for the first time, because I would like to create Addons for Node.js - https://nodejs.org/api/addons.html – Alexander Mills Jan 07 '17 at 11:53
  • @AlexanderMills: That's a static final field and has nothing to do with what I said. Java does not have any equivalent for `const` member functions; there is no way to disallow modification of non-static fields in a Java method. – Christian Hackl Jan 07 '17 at 11:55
  • @AlexanderMills: By the way... writing a Node Addon, or tackling 3rd-party libraries in general, may not be the best way to learn C++. You should start with smaller things if you don't want to get totally confused and give up early on. – Christian Hackl Jan 07 '17 at 11:58
  • Yeah I got things to work so far, but it's all just one file. Writing a simple addon with just one .cpp file isn't so bad, it's not easy but I can get it to work. I do agree that just writing C first and then learning C++ after learning C would probably be the best approach. I just don't have any good reason to use C at the moment. – Alexander Mills Jan 07 '17 at 12:00
  • @AlexanderMills: I was not talking about learning C first. I was talking about starting with clean C++ programs, without any Node stuff. Once you understand how C++ works, *then* try to connect it to Node. Learning C is an excellent idea if you want to get a *real* understanding of how your computer works below those hundreds of abstraction layers you are used to, or if you are interested in computer science in general, but you must try to keep C and C++ mentally separated because good C code is often bad C++ code. – Christian Hackl Jan 07 '17 at 12:04
  • re: java and final, I get a compile error if I try to compile this: " public class Foo { private final int z = 0; public void modify(){ this.z = 5; } public static void main(){ new Foo().modify(); } }" – Alexander Mills Jan 07 '17 at 12:06
  • lib/Foo.java:9: error: cannot assign a value to final variable z this.z = 5; ^ – Alexander Mills Jan 07 '17 at 12:07
  • @AlexanderMills: Yes, that's the point of it. You made `z` final so that the compiler can catch accidental modification. Now you did *accidentally* try to modify it. The compiler complains. You can fix the code. Or you suddenly realise that `z` should not be `final`, after all. You can fix that, too, by removing the `final`. – Christian Hackl Jan 07 '17 at 12:08
  • yeah one thing that confuses me about C++, I thought version 14 came out, but now when I double checked we are on C++11, what's that about. – Alexander Mills Jan 07 '17 at 12:09
  • or maybe Node.js is only designed to work with C++11 – Alexander Mills Jan 07 '17 at 12:10
  • @AlexanderMills: You sure have a lot of questions. It would be a good idea to purchase one or two good books on the subject, and read them. In any case, C++ does not really "come out". The ISO publishes standard documents, which compiler implementors (such as for example Microsoft or the GCC community) then implement, or try to implement as completely as possible. The C++11 ISO standard was a major step forward, so much that many consider it a new language. C++14 was a minor extension of C++11. It's your job to choose a compiler which supports the features you need. – Christian Hackl Jan 07 '17 at 12:13
  • huh, that's interesting - maybe you can help me with this C++ code and give it a once over - https://github.com/ORESoftware/replace-line – Alexander Mills Jan 07 '17 at 12:27
  • the only file to look at is: https://github.com/ORESoftware/replace-line/blob/master/hello.cpp, that is the one file that runs – Alexander Mills Jan 07 '17 at 12:29
  • @AlexanderMills: Sorry, but I already have a job with a regular income and have no intention of starting extra work as a paid C++ consultant :) However, this may be a good excuse for you to start learning how to ask questions on Stack Overflow. Turn your problem into an MCVE (that's important, or the question will be closed) and post it here, explaining what exactly is wrong. Or if you just need a code review, try codereview.stackexchange.com, because Stack Overflow is not for code reviews. – Christian Hackl Jan 07 '17 at 12:33
  • yeah code review is a good idea, one excellent reason to do that is that I get 30 warnings when I compile that file :) thanks anyway though Mr. C++ :) – Alexander Mills Jan 07 '17 at 12:36
  • @AlexanderMills: You're welcome. Strive for warning-free code (in every language) and always use a reasonably high warning level, because the default is often too low. – Christian Hackl Jan 07 '17 at 12:39
  • Just in case you want to do unpaid labor :) http://codereview.stackexchange.com/questions/151962/reduce-compile-warnings-in-c-file-node-js-addon – Alexander Mills Jan 07 '17 at 12:43
1

So here is the simplest way I learned how to do it (and I think it is even more straightforward than the main answer). This will work on any type in the tuple that can be compared with <.

Here is the sort function TupleLess, which takes advantage of some C style syntax:

 #include <string>
 #include <vector>
 #include <tuple>      

 template<int index> struct TupleLess
 {
    template<typename Tuple>
    bool operator() (const Tuple& left, const Tuple& right) const
    {
        return std::get<index>(left) < std::get<index>(right);
    }
};

int main(){
    //Define a Person tuple:    
    using Person = std::tuple<std::string, std::string, std::string>;

    //Create some Person tuples and add to a vector
    Person person1 = std::make_tuple("John", "Smith", "323 Fake Street");
    Person person2 = std::make_tuple("Sheila", "Henrod", "784 Unreal Avenue");
    Person person3 = std::make_tuple("Mitch", "LumpyLumps", "463 Falsified Lane");

    std::vector<Person> vPerson = std::vector<Person>();
    vPerson.push_back(person1);
    vPerson.push_back(person2);
    vPerson.push_back(person3);

    //Sort by index at position 2
    std::sort(vPerson.begin(), vPerson.end(), TupleLess<2>());
}
EliSquared
  • 1,409
  • 5
  • 20
  • 44