Problem overview
sort can order numbers and strings because C++ already knows which of two of them comes first. For objects of your own class it knows nothing, until the class says so. In C++ a class says so by defining operator<. Once it exists, sort can order a vector of those objects, and so can the expression a < b.
You write two things:
- a class
Student, created withStudent(name, grade), whoseoperator<says that a student comes first if the grade is higher, and, when the grades are equal, if the name comes first in alphabetical order; - the method
rankStudents(students)inSolution, which sorts with the language's ownsortand returns the names in the new order.
For Ana (90), Bob (85) and Cid (90) the result is Ana Cid Bob. Ana and Cid tie at the highest grade, so their names decide, and Ana comes before Cid. Bob, with 85, comes last.
Approach: define "comes before", then let sort do the work
Writing a sorting loop would be extra work and an extra place for bugs. The better idea is to teach the class its own order and call sort with no comparison argument. sort uses < on the elements, so it picks up your operator<.
What operator< must answer is: "does this student come before other?" That has two rules, in this order:
- If the grades differ, the higher grade comes first. So the result is whether
grade > other.grade. Notice the direction: the operator is called<, but "less" here means "earlier in the ranking", so the higher grade is the one that is "smaller". - If the grades are equal, the names decide. Comparing two strings with
<goes letter by letter, so names go from A to Z. When one name is the start of another, the shorter one is first:Anncomes beforeAnna.
The names are all different, so two students are never equal on both rules.
Step by step
- Under
public:, declarestring name;andint grade;. They are public becauserankStudentsreads each name. - In the constructor, store the parameters with
this->name = name;andthis->grade = grade;. - In
operator<, check whether the grades differ. If so, returngrade > other.grade. - Otherwise return
name < other.name. - In
rankStudents, callsort(students.begin(), students.end()), with no third argument. - Declare an empty
vector<string> namesand loop over the sorted students, pushing eachstudent.name. - Return
names.
The solution in C++
class Student {
public:
string name;
int grade;
Student(string name, int grade) {
this->name = name;
this->grade = grade;
}
bool operator<(const Student& other) const {
if (grade != other.grade) {
return grade > other.grade;
}
return name < other.name;
}
};
class Solution {
public:
vector<string> rankStudents(vector<Student> students) {
sort(students.begin(), students.end());
vector<string> names;
for (Student& student : students) {
names.push_back(student.name);
}
return names;
}
};
A run
Ranking the students of the other two examples gives:
Dan Cal Eve Bea
Ann Anna Leo Max
The first list was Eve (55), Dan (100), Bea (0) and Cal (99). All the grades differ, so only the grades decide. In the second list all four students have 88, so the list is in alphabetical order.
The operator also works by itself. With Student a("Ana", 90), Student c("Cid", 90) and Student b("Bob", 85), the expressions a < c, c < a, a < b, b < a and a < a give true, false, true, false and false. The last one matters: a student never comes before itself. That matches what sort expects of a comparison.
Details that matter
- The header.
bool operator<(const Student& other) consttakes the other student by reference, so nothing is copied, and theconstafter the parameter list promises that comparing doesn't change the student. Both are the usual way to write it. - Parameters and fields. They share their names in the constructor, so
this->nameis the field and plainnameis the parameter. - The vector is a copy.
rankStudentstakes the vector by value, so sorting it doesn't change the caller's vector. - The loop variable.
Student& studentuses each element in place instead of copying it.
Complexity
For n students, sort makes O(n log n) comparisons, and each comparison takes constant time for names of at most 10 letters. Building the list of names takes O(n). The total is O(n log n) time. The copy of the vector and the vector of names take O(n) extra space.
Edge cases and common mistakes
- The wrong direction on grades. With
grade < other.grade, the lowest grade ranks first. Ana, Bob and Cid come out asBob Ana Cidinstead ofAna Cid Bob. - The wrong direction on names. With
name > other.namefor equal grades, Ana and Cid swap, and the all-88 list comes out asMax Leo Anna Ann, from Z to A. - Forgetting the name rule. If the operator only compares grades, students with equal grades are considered equal, and
sortdoesn't promise any particular order for equal elements. The names must be the tie-breaker. - Returning
truefor equal students. An operator written with<=or>=breaks the rule thatsortrelies on: a value is never before itself. Use strict comparisons. - Writing your own sorting loop. The lesson wants
sortwith the class's own comparison.
Testing
| Students | Result |
|---|---|
| Ana 90, Bob 85, Cid 90 | Ana Cid Bob |
| Eve 55, Dan 100, Bea 0, Cal 99 | Dan Cal Eve Bea |
| Max 88, Leo 88, Anna 88, Ann 88 | Ann Anna Leo Max |
