0

I have a vector of a user type object and want to sort the vector by the first string variable and then by the second string variable.

class MyClass{
  private:
    string a;
    string b;
    int x;
    double y;
}

I have a vector in the main code that has the data already parsed into any number of elements depending on the file.

int main(){
  vector<MyClass> data;

  // Code that fills the data variable.  This all works and can be displayed via print function

  /*CODE TO SORT BY THE FIRST STRING THEN SORT AGAIN BY THE SECOND STRING
   *
   *  -- Sort code here --
   *
   */

  return 0;
}

My question is 2 fold:

1) How do you do a sort of the vector based on a variable inside that vector? The vector should be sorted based on the first string in the class (the string labeled a).

2). How would you further sort the vector so that once the first string is sorted, sort the second string so that the output may look something like this (for all intents and purposes the numbers in the second string (string b) are strings not integers):

string a: a    string b: 1
string a: a    string b: 2
string a: a    string b: 3
string a: a    string b: 4
string a: b    string b: 1
string a: b    string b: 2
string a: b    string b: 3
string a: b    string b: 4
TheWinterSnow
  • 175
  • 11
  • Use `CustomComparator`, i.e., pass your own `Compare` function to `std::sort`. Try that and then edit your question to ask the problems you face. – CinCout Oct 25 '18 at 06:36
  • 1
    [`void main()` is invalid C++](https://stackoverflow.com/q/204476/995714) – phuclv Oct 25 '18 at 06:48

5 Answers5

1

Make a and b public and try something like this:

std::sort(data.begin(), data.end(), [](const MyClass& v1, const MyClass& v2) {
        return (v1.a == v2.a) ? (v1.b < v2.b) : (v1.a < v2.a);   
    });

You can keep them private and use getter for getting their value too. As ZDF mentioned, you can create < operator too. add following to Myclass:

MyClass {
...
public:
    bool operator<(const MyClass& v2) const {
        return (a == v2.a) ? (b < v2.b) : (a < v2.a);   
    }
}

Then sort like this:

std::sort(data.begin(), data.end());
Afshin
  • 8,839
  • 1
  • 18
  • 53
  • The primitive data types in my class are private. – TheWinterSnow Oct 25 '18 at 06:59
  • then create `<` operator. I add it to reply. – Afshin Oct 25 '18 at 07:03
  • Ok that worked with a little bit of code modification but can anyone explain to me how an operator overload (operator <) in a class and using a ternary operator changed the behavior of the sort function? – TheWinterSnow Oct 25 '18 at 07:36
  • @TheWinterSnow `std::sort` uses a comparator function for comparing 2 elements of array for sorting. when you provide a `<` operator for your class, it uses this operator as comparator function. If you don't provide this in class, you need to provide a specific comparator function for `sort`. and how this function sorts is somehow obvious (compare first, if not equal compare second) – Afshin Oct 25 '18 at 08:03
1

std::sort supports two ways to compare items: 1. bool operator<(YourClass const & lhs, YourClass const & rhs); 2. a comparator that is passed into std::sort

How to make a lexicographic comparison of your class. The best way to use std::tuple that provides such functionality:

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

struct MyClass{
    std::string a;
    std::string b;
    int x;
    double y;
    std::tuple<std::string const &, std::string const &> asTupleForComparison() const
    {
        return std::tie(a, b);
    }
};


int main() 
{ 
    std::vector<MyClass> vec = {{std::string("a"), std::string("b"), 0, 0.0}, 
                                {std::string("a"), std::string("a"), 0, 0.0}};
    std::sort(vec.begin(), vec.end(), [](MyClass const & lhs, MyClass const & rhs){
        return lhs.asTupleForComparison() < rhs.asTupleForComparison();
    });

    for (auto const & item : vec)
    {
        std::cout << item.a << " " << item.b << "\n";
    }
    return 0; 
}

to resolve private variables issue use friend:

class MyClass{
    std::string a;
    std::string b;
    int x;
    double y;
    std::tuple<std::string const &, std::string const &> asTupleForComparison() const
    {
       return std::tie(a, b);
    }
    friend bool compare(MyClass const & lhs, MyClass const & lhs)
    {
        return lhs.asTupleForComparison() < rhs.asTupleForComparison();
    }
};
Alexander
  • 698
  • 6
  • 14
0

In addition to other methods, you can even declare a custom comparator in friend class compare inside MyClass.

Something like this:

class MyClass{
  private:
   string a;
   string b;
   int x;
   double y;
  public:
   friend class compare;
}

Check this stackoverflow answer for more details on how to define custom comparators.

arjunkhera
  • 929
  • 6
  • 23
0

This is a simplfied version of my working code:

MyClass.h

class MyClass{
  public:
    friend bool operator > (const MyClass& lhs, const MyClass& rhs);

  private:
    string a;
    string b;
    int x;
    double y;
}

MyClass.cpp

bool operator < (const MyClass& lhs, const MyClass& rhs){
    return (lhs.a == rhs.b) ? (lhs.b < rhs.b) : (lhs.a < rhs.b);
} 

Main.cpp

int main(){
  vector<MyClass> data;  // Already filed data

  sort(data.begin(), data.end());

  return 0;
}
TheWinterSnow
  • 175
  • 11
0

You might pass custom comparer. std::tie might help to create it simply as std::tuple has lexicographic comparison (as long as its inner type can be compared):

std::vector<MyClass> data/* = ..*/;

auto as_tuple_a_b = [](const MyClass& c) { return std::tie(c.a, c.b); };
std::sort(data.begin(), data.end(),
          [](const MyClass& lhs, const MyClass& rhs) {
              return as_tuple_a_b (lhs) < as_tuple_a_b(rhs);
          });

If it makes sense, you might implement operator < for your type:

bool operator< (const MyClass& lhs, const MyClass& rhs) {
    auto as_tuple = [](const MyClass& c) { return std::tie(c.a, c.b, c.x, c.y); };
    return as_tuple (lhs) < as_tuple_a_b(rhs);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302