简体   繁体   中英

std sort seems to be looping forever

Right now I have a class to do a binary search. The class accepts a vector, but then I tell the class to sort.

I need to be able to have it sort by only first name potentially or last name, so I set a character argument as a choice in that class to change how I sort the vector. I also in that class made an operator() function to use *this, as a class pointer to sort the vector. But it seems to just be looping forever. Can anyone tell me why? Code Below.

*note if there's some general practices I'm not following feel free to inform me. I don't want to start making bad habits now.

By request: Getname

void personType::getName(string& first, string& last)
{
    // get the name and set it
    first = firstName;
    last = lastName;
}


bool sBinary::operator()(studentType student1, studentType student2){
    string toCheck1, toCheck2, fName1,lName1 ,fName2 , lName2;
    student1.getName(fName1, lName1);
    student2.getName(fName2, lName2);
    toCheck1=checkStr(fName1, lName1);
    toCheck2=checkStr(fName2,lName2);
    return toCheck1<toCheck2;
}

string sBinary::checkStr(string fName, string lName){
    string toCheck;
    switch (choice){
    case 'f':
    case 'F':
        toCheck=fName;
        break;
    case 'l':
    case 'L':
        toCheck=lName;
        break;
    case 'r':
    case 'R':
        toCheck=fName+lName;
        break;
    default:
        toCheck=lName+fName;

    }

    return toCheck;

}


sBinary::sBinary(vector<studentType> _sList, char _choice){
    sList=_sList;
    steps=0;
    choice=_choice;
    sort(sList.begin(),sList.end(), *this);
}

So, it seems not not to loop forever, but executes too long. It's completely different story. You have a couple of pessimisations in your code: The main concern is that you pass *this , to the sorting algorithm:

sort(sList.begin(),sList.end(), *this);

std::sort takes comparation predicate by value and it copies it many times. You can see it, if you define copy constructor:

sBinary(const sBinary& r):choice(r.choice), sList(r.sList)
{
    std::cout << "copied\n";
}

And your vector gets copied along with the object itself.

For example, if the array size is 200, std::sort copies object 13646 times . It means, that 2700000 student copy operations involved.

So, you should not pass *this to std::sort . You'd better define static function lessThen instead of operator() and pass it to sorting algorithm.

Further improvements:

  1. Pass by reference , rather then by value. For example, in your lessThen function declaration should look like

     static bool lessThen(const studentType& student1, const studentType& student2); //^^^^^ ^ //constant reference 
  2. Refactor your studentType class.

    You'd better have 2 separate functions, returning first and last name (by constant reference). In this case you could get rid of copying names to temporary variables. Note, that when you have single function, you have to copy both first and last name, even if one name will never be used:

     const std::string& first_name() const { return _fname; } const std::string& last_name() const { return _lname; } 

I'm including this only because you should know alternatives to how you're sorting this list. Lol4t0 has already talked about the hideousness of having a comparator that is expensive to copy (and you would be hard pressed to have one more expensive than your original implementation).

The std::sort algorithms work best when given as simple a comparator as possible, with as much chance for inlining it's implementation as it can get. Ideally you implement a comparator operator function like this:

struct cmpObjects
{
    bool operator ()(const Object& left, const Object& right) const
    {
        return (left compared to right somehow);
    }
}

First notice the use of const references. The only time you should consider NOT doing this is if your underlying data is an native intrinsic type (such as int , char , etc.). In those cases it is actually faster to pass-by-value. But in this case, your student records are most-assuredly more efficient to access by reference (no copying).

Regarding your specific task, yours is a little more complicated based on the fact that you're sorting criteria is choice-based. If you want to maximize sort-speed you ideally have a single, tight, cheaply copyable comparator for each choice case. Then, use the proper comparator based on that choice, determined before invoking std::sort .

For example, if you know you're sorting on last name, then:

// compares last name
struct cmp_LName
{
    bool operator ()(const studentType& left, const studentType& right) const
    {
        return left.lastName < right.lastName;
    }
}

or perhaps first name, last name such as:

// compares first name, then last name only if first name is identical.
struct cmp_FNameLName
{
    bool operator ()(const studentType& left, const studentType& right) const
    {
        int res = left.firstName.compare(right.firstName);
        return res < 0 || (res == 0 && left.lastName < right.lastName);
    }
}

This makes a partial peek at your sBinary constructor now look like this:

sBinary(const std::vector<studentType>& sList_, char choice)
    : sList(sList_)
{
    switch (choice)
    {
        case 'L':
        case 'l':
            std::sort(sList.begin(), sList.end(), cmp_LName());
            break;

        case 'R':
        case 'r':
            std::sort(sList.begin(), sList.end(), cmp_FNameLName());
            break;

        ....
    }
}

Notice first we're making the choice for what comparison technique we're choosing prior to actually calling std::sort . When we do, we have the clear definition of what exactly that criteria is within the custom comparator we're using, and zero overhead it managing it.

So whats the trade off? You would need four comparators (cmp_LName, cmp_FName, cmp_FNameLName, and cmp_LNameFName), triggering which to use based on your incoming choice. However, the benefit for doing so cannot be overstated: This will be the fastest way to sort your list based on choice.


Addendum: Single Comparator

If you are absolutely positively married to the idea of using a single comparator, then make it as cheap to copy as possible, and bury the choice made in the sorting condition within it as const to give the compiler the best chance of cleaning up your code. I've included a full expansion of sBinary below to show how this can be done, but I stress, this is not optimal if speed is your primary concern.

class sBinary
{
    // compare student based on fixed choice determine at construction.
    struct cmp_student
    {
        const char choice;
        cmp_student(char choice) : choice(choice) {};

        bool operator()(const studentType& left, const studentType& right) const
        {
            switch (choice)
            {
                case 'F':
                case 'f':
                    return left.firstName < right.firstName;

                case 'L':
                case 'l':
                    return left.lastName < right.lastName;

                case 'R':
                case 'r':
                {
                    int res = left.firstName.compare(right.firstName);
                    return res < 0 || (res == 0 &&  left.lastName < right.lastName);
                }

                default:
                {
                    int res = left.lastName.compare(right.lastName);
                    return res < 0 || (res == 0 &&  left.firstName < right.firstName);
                }
            }
        }
    };

public:
    sBinary(const std::vector<studentType>& sList, char choice)
        : sList(sList)
    {
        std::sort(sList.begin(), sList.end(), cmp_student(choice));
    }

    std::vector<studentType> sList;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM