Start learning free

LESSON 563 OF 569 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Sort Your Own Objects in JavaScript: compareTo and sort

Practice O(n log n) Read the full explanation

A language can sort numbers and strings on its own: it already knows which of two of them comes first. For objects of your own class it doesn't, until the class tells it. Every language has a way for a class to say how two of its objects compare. Once a class has it, the language's own sort can sort a list of those objects, with no comparison written at the call.

Write:

  • a class Student, created by Student(name, grade), that can compare itself with another student: a student comes first if their grade is higher; when two grades are equal, the name that comes first in alphabetical order (A to Z) comes first;
  • a method rankStudents(students) in the class Solution, which sorts the students with your language's own sort, using that comparison, and returns their names in that order.

JavaScript has no comparison interface. The convention is a method compareTo(other) that returns a negative number when this student comes first, a positive number when other does, and 0 when they are equal; sort gets a function that calls it.

Don't write your own sorting loop. The program also checks your comparison directly: for each two neighbours in the list you return, it asks Student whether the first one comes before the second.

 

Example 1:

Input: students = [("Ana", 90), ("Bob", 85), ("Cid", 90)]
Output: Ana Cid Bob
Explanation: Ana and Cid have the highest grade, 90. They tie, so their names decide: Ana comes before Cid.
Bob, with 85, comes last.

Example 2:

Input: students = [("Eve", 55), ("Dan", 100), ("Bea", 0), ("Cal", 99)]
Output: Dan Cal Eve Bea
Explanation: All the grades are different, so only the grades decide, from 100 down to 0.

Example 3:

Input: students = [("Max", 88), ("Leo", 88), ("Anna", 88), ("Ann", 88)]
Output: Ann Anna Leo Max
Explanation: Everyone has the same grade, so the list is in alphabetical order.
"Ann" comes before "Anna": when one name is the start of the other, the shorter one comes first.

 

Constraints:

  • There are 1 to 100 students.
  • 0 <= grade <= 100
  • A name has 2 to 10 letters: a capital letter, then lowercase letters. No two students have the same name.
Note:

The language's sort takes O(n log n) comparisons for n students, and each comparison takes constant time.

STEP 1 OF 8
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Sort Your Own Objects in JavaScript, explained

    Problem overview

    JavaScript can sort numbers and strings because it knows which of two comes first. For objects of your own class it knows nothing, until the class says how two of its objects compare. The task is to write a class Student, created as new Student(name, grade), that can compare itself with another student, and a method rankStudents(students) in the class Solution that sorts with the array's own sort and returns the names in rank order.

    The ranking rule: a student comes first if their grade is higher. When two grades are equal, the name that comes first from A to Z comes first.

    For Ana (90), Bob (85) and Cid (90), the answer is Ana, Cid, Bob. Ana and Cid tie on the highest grade, so their names decide, and Bob's 85 puts him last.

    Approach: the class holds the rule, sort uses it

    JavaScript has no comparison interface. The convention is a method compareTo(other) that returns a negative number when this student comes first, a positive number when other does, and 0 when they are equal. That is the same contract the array's sort expects from a comparison function: it receives two items and returns a negative, positive or zero number. So rankStudents passes sort a small function that hands the two students to compareTo, and the rule lives in one place, the class.

    No sorting loop is written. The sort performs O(n log n) comparisons for n students, and each comparison takes constant time.

    Step by step

    1. Store the data. The constructor puts name and grade on this. They stay public, because rankStudents reads each student's name.
    2. Compare grades first. If the grades differ, return other.grade - this.grade. When this student's grade is higher, the difference is negative, so this student comes first.
    3. Break ties by name. When the grades are equal, return this.name.localeCompare(other.name). It is negative when this.name comes first alphabetically, so equal grades go from A to Z.
    4. Sort in rankStudents. Call students.sort with (first, second) => first.compareTo(second).
    5. Collect the names. Create an empty array names, loop over the sorted students with for...of, push each student.name, and return names.

    The solution in JavaScript

    class Student {
        constructor(name, grade) {
            this.name = name;
            this.grade = grade;
        }
    
        compareTo(other) {
            if (this.grade !== other.grade) {
                return other.grade - this.grade;
            }
            return this.name.localeCompare(other.name);
        }
    }
    
    class Solution {
        rankStudents(students) {
            students.sort((first, second) => first.compareTo(second));
            let names = [];
            for (let student of students) {
                names.push(student.name);
            }
            return names;
        }
    }
    

    Why the subtraction is reversed

    For ascending order you would return this.grade - other.grade. The ranking here is descending, so the operands swap: other.grade - this.grade. With Ana (90) and Bob (85), new Student("Ana", 90).compareTo(new Student("Bob", 85)) returns -5: negative, so Ana first. The call the other way round returns 5, and two equal students give 0.

    Strings with localeCompare

    Strings can be compared with localeCompare, which returns a negative number, zero or a positive number. For "Ann" and "Anna" it returns -1: when one name is the start of the other, the shorter one comes first.

    Sorting changes the array

    sort reorders the array it is called on and returns that same array. After rankStudents(students), the caller's students array is in rank order too. Without a comparison function, sort would compare the objects as text and ignore your rule. Even for plain numbers it compares as strings: [10, 9, 1].sort() gives [1, 10, 9].

    Using the classes

    const students = [
        new Student("Eve", 55),
        new Student("Dan", 100),
        new Student("Bea", 0),
        new Student("Cal", 99)
    ];
    console.log(new Solution().rankStudents(students));
    [ 'Dan', 'Cal', 'Eve', 'Bea' ]

    All grades differ, so only the grades decide, from 100 down to 0. For Max, Leo, Anna and Ann, all with grade 88, the names alone decide and the result is Ann, Anna, Leo, Max.

    How sort uses the comparison

    The array's sort does not know anything about students. It repeatedly takes two items from the array, calls the function it was given with them, and looks only at the sign of the number it gets back. A negative number means the first item goes before the second, a positive number means after, and zero means the two are tied. Because the function here forwards to compareTo, any change to the ranking rule is made in the class and nowhere else.

    The lesson's tests also call compareTo directly, on each two neighbours of the list you return, and expect the first to come before the second. That works only because the comparison and the ranking are the same rule, written once.

    Edge cases and common mistakes

    • Wrong sign. this.grade - other.grade ranks the lowest grade first. If the output is backwards, check the order of the operands.
    • Forgetting the tie. Returning only the grade difference gives 0 for equal grades, and the names stay in whatever order the input had.
    • Returning a boolean. A comparison must return a number. A function that returns true or false does not tell sort which item is first, so the result is unreliable.
    • Calling sort() with no function. The default compares text, not your rule.
    • Returning students instead of names. The method returns the names.
    • One student. The sort has nothing to compare and the result is a one-name array.

    Key takeaways

    • Put the comparison in the class as compareTo, and let sort call it.
    • Negative means first, positive means second, zero means equal.
    • Compare the main key first, then break ties with the next key.
    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free