Problem overview
Python can sort numbers and strings because it knows which of two of them comes first. For objects of your own class it does not, until the class says how two of them compare. The lesson asks for:
- a class
Student, created withStudent(name, grade), where a student comes first if the grade is higher, and when the grades are equal the name that comes first alphabetically comes first; - a method
rank_students(students)in the classSolutionthat sorts the students with the list's own sort and returns their names in that order.
For Ana (90), Bob (85) and Cid (90), the result is Ana Cid Bob. Ana and Cid tie on 90, so their names decide, and Bob has the lowest grade.
Approach: teach the class to compare, then let sort do the work
Writing your own sorting loop is not needed. Python's list.sort() only asks one question about two items: does the first one come before the second? It asks it with the < operator. When a < b is written for two objects of your class, Python calls a.__lt__(b). If Student defines __lt__, sorting a list of students just works, with no key argument.
The method returns True when self comes before other. Here that means two rules, checked in order:
- If the grades differ, the student with the higher grade comes first, so return
self.grade > other.grade. The comparison is reversed on purpose: "first" means "highest grade". - If the grades are equal, compare the names with
<. Python compares strings letter by letter, so"Ana" < "Cid"isTrue, and when one name is the start of another the shorter one is smaller:"Ann" < "Anna"isTrue.
Step by step
- Store the data. In
__init__, setself.nameandself.grade. They stay public becauserank_studentsreads each name. - Handle different grades. In
__lt__, ifself.grade != other.grade, returnself.grade > other.grade. - Handle equal grades. Return
self.name < other.name. - Sort in place. In
rank_students, callstudents.sort()with no arguments. - Collect the names. Start an empty list
names, loop over the sorted students and appendstudent.name. - Return
names.
The solution in Python
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def __lt__(self, other):
if self.grade != other.grade:
return self.grade > other.grade
return self.name < other.name
class Solution:
def rank_students(self, students):
students.sort()
names = []
for student in students:
names.append(student.name)
return names
Using it on the examples:
solution = Solution()
data = [("Ana", 90), ("Bob", 85), ("Cid", 90)]
print(*solution.rank_students([Student(n, g) for n, g in data]))
data = [("Eve", 55), ("Dan", 100), ("Bea", 0), ("Cal", 99)]
print(*solution.rank_students([Student(n, g) for n, g in data]))
data = [("Max", 88), ("Leo", 88), ("Anna", 88), ("Ann", 88)]
print(*solution.rank_students([Student(n, g) for n, g in data]))
Ana Cid Bob
Dan Cal Eve Bea
Ann Anna Leo Max
The comparison can also be used directly:
a = Student("Ana", 90)
b = Student("Bob", 85)
print(a < b, b < a)
True False
Ana comes before Bob because 90 is higher than 85, so a < b is True even though 90 is not less than 85. In this class, "less than" means "ranks earlier".
Why only __lt__
sort() and sorted() use only <, so __lt__ is the one method needed for sorting. Without it, sorting a list of your objects fails:
TypeError: '<' not supported between instances of 'NoLt' and 'NoLt'
That message comes from a class named NoLt that has no __lt__. Operators such as >, <= or == are separate methods (__gt__, __le__, __eq__). They are not needed here, and the tests check only the order of the names.
Edge cases and common mistakes
- Reversed grade comparison. Writing
self.grade < other.gradeputs the lowest grade first. The lesson wants the highest first. - Reversing the name comparison too. Names go A to Z, so use
<for the name and>only for the grade. - Prefix names.
"Ann"comes before"Anna": the shorter name is smaller when it is the start of the other. - Lowercase and uppercase. Python compares letters by code, so any uppercase letter is smaller than any lowercase one. The lesson's names are a capital letter followed by lowercase letters, so this does not come up.
- Returning the students instead of the names. The method returns a list of
namestrings. - Sorting a copy.
students.sort()changes the list and returnsNone. If you want to keep the original order, usesorted(students), which returns a new list.
Complexity
Python's sort makes O(n log n) comparisons for n students, and each comparison takes constant time, so sorting is O(n log n). Collecting the names is another O(n).
