Lotayou
Lotayou

Reputation: 162

A function template that can sort object array with respect to any attributes

I'm the headmaster of a very large public school and I've been watching my kids growing up for ten years. Their basic info and test scores each year are stored as follows:

class KidInfo{
    string name;
    int age;
    vector<int> score; //The lengths increases as my kids grows up
};

const int NUM = 100000;  //Wow, that's gigantic my friend

KidInfo myKids[NUM];

Now I want to analyze these results and create their study profile. To do this I need to sort the array myKids by their test scores in descending order each year, for ten years. I have to use std::sort to guarentee efficincy since I'm running quite a large school.

I'm not a very skillful C++ programmer and have no idea how to handle this task. After checking the answer sort multidimensional vector by 1st column, I wrote the following code:

bool cmpByYear1(const KidInfo &k1, const KidInfo &k2){
    return k1.score[0] > k2.score[0];
}

bool cmpByYear2(const KidInfo &k1, const KidInfo &k2){
    return k1.score[1] > k2.score[1];
}

//And 8 similiar functions

sort(muKids, myKids + NUM, cmpByYear1);
//print Year 1...
sort(muKids, myKids + NUM, cmpByYear2);
//print Year 2, and so on...

It didn't take too long before I got bored writing a new cmpByYearN function each year, so I'm thinking about more elegant approaches, like template:

template<int year>
bool cmpAnyYear(const KidInfo &k1, const KidInfo &k2){
    return k1.score[year - 1] > k2.score[year - 1];
}

int main(){
    //...
    for(int year = 1; year <= 10; ++year){
        sort(myKids, myKids + NUM, cmpAnyYear<year>);
    }
    //...
    return 0;
}

Unfortunately my code got compile error with messages like “template parameter "year": local variables cannot be used as non-type parameter...”.

So I was thinking using a global parameter instead:

int globalYear = 1;

bool cmpAnyYear(const KidInfo &k1, const KidInfo &k2){
    return k1.score[globalYear - 1] > k2.score[globalYear - 1];
}

int main(){
    //...
    for(; globalYear <= 10; ++globalYear){
        sort(myKids, myKids + NUM, cmpAnyYear);
    }
    //...
    return 0;
}

This time my code actually runs, but sadly it still doesn't work: It looks like the cmpAnyYear function was already fixed when I declared globalYear = 1; and behaved just like cmpByYear1 regardless of the subsequent changes of globalYear. The sorting results remain unchanged from year 2.

Upvotes: 2

Views: 476

Answers (2)

bolov
bolov

Reputation: 75727

Use a lambda which captures the year:

int main()
{
    //...
    for (int year = 1; year <= 10; ++year) {
        sort(myKids, myKids + NUM, [year](const KidInfo& k1, const KidInfo& k2) {
            return k1.score[year - 1] > k2.score[year - 1];
        });
    }
    //...
    return 0;
}

The reason why year as template parameter didn't work is because it has to be a compile-time constant when you call it, which is not in your for loop.

Upvotes: 1

Some programmer dude
Some programmer dude

Reputation: 409196

You can use functor objects, like e.g.

struct cmpAnyYear
{
    cmpAnyYear(int year)
        : year_(year)
    {}

    bool operator()(KindInfo const& k1, KidInfo const& k2) const
    {
        return k1.score[year_ - 1] > k2.score[year_ - 1];
    }

    int year_;
}

int main(){
    //...
    for(int year = 1; year <= 10; ++year){
        sort(myKids, myKids + NUM, cmpAnyYear(year));
    }
    //...
    return 0;
}

The expression cmpAnyYear(year) constructs a (temporary) object of the type cmpAnyYear and passes year as argument to the constructor. This object is the "called" as a function, which calls the cmpAnyYear::operator() for the comparison.

Upvotes: 2

Related Questions