Assignment 4 — Ordering

Teach Matrix its natural order so Arrays.sort will sort it, then write comparators for the orders it does not have. Find the place where compareTo and equals disagree, and fix it. Then do the whole thing again to Fraction, where the disagreement never happens.

Due Sep 22, 2026

Note: “AI” below means your favorite chatbot, e.g. Google Gemini, Microsoft Copilot, ChatGPT. Where a step does not say otherwise, AI tools are not allowed, as set out in the AI policy in the syllabus.

This one has its own repo. Accept it the same way you accepted A3, and clone it. What you get is two classes you have already met, both finished: the Matrix from A3, with every method written and every test passing, and the Fraction from A2. Neither one is yours to fix. This assignment is about ordering.

Your own Matrix or Fraction is welcome instead. If yours works and you would rather build on it, copy your file over the one in the new repo and commit that before you start — one commit, on its own, so the swap is visible in the history. Both ways are fine and neither earns or loses anything. Nothing below cares which one you kept.

Build and run the same way as before:

javac -d out src/*.java
java -ea -cp out MatrixTest

A floor, if you run out of time. Tasks 1 to 4 are the ones to get working. They are the whole of the idea — one order that the library knows how to use, and one more that you supply yourself — and everything after them is a variation on it. Push what you have even if it is only the floor. Partial work you pushed beats finished work you did not.

Parts 3 and 4 are the ones to start early if you can. Not because they are worth more, but because each of them asks you to write down a prediction before you run the code, and that goes badly in a hurry. A guess you wrote and then watched fail is the part that teaches; a guess you skipped to save four minutes is four minutes.

Part 1 — the order a matrix already has

  1. Make Matrix implement Comparable<Matrix>. Add implements Comparable<Matrix> to the class declaration and write the one method it requires:

    @Override
    public int compareTo(Matrix other)

    Order matrices by magnitude(), smallest first. magnitude() is already there.

    The method returns any negative number when this comes first, any positive number when it comes second, and zero when neither does. It does not have to be -1, 0 and 1.

    Do not subtract two magnitudes and cast the result to int. Magnitudes are doubles, and two matrices 0.4 apart both cast to 0 — so the sort quietly decides they are equal. Double.compare does this correctly in one line; look at what it returns before you use it.

  2. Sort an array. Write src/SortMatrices.java with a main that builds an array of at least four matrices with different magnitudes, prints the array, calls Arrays.sort on it, and prints it again.

    You need import java.util.Arrays; for this.

    Nothing in Arrays knows anything about your class. It works because task 1 kept a promise.

Part 2 — the orders it does not have

  1. Write src/MagnitudeDescending.java, a class implementing Comparator<Matrix>, that orders matrices largest first.

    Get the reverse order by comparing the two matrices the other way round, not by negating the result of compareTo. Negating looks equivalent and is not: the most negative int has no positive counterpart, so negating it gives you back a negative number and the order silently breaks.

  2. Sort the same array with it, in the same main, and print the result. Arrays.sort takes a comparator as a second argument.

    Matrix did not change between task 2 and task 4. That is the point of this part: the sorted order is not a property of the class.

  3. Write src/BySize.java, a Comparator<Matrix> that orders matrices by row count, and breaks ties by column count. Sort with it and print the result.

    Two matrices with the same shape are equal as far as this comparator is concerned, however different their numbers are. That is allowed, and Part 3 is about what it costs.

Part 3 — where the two contracts disagree

  1. Build two matrices that are different but have the same magnitude, and predict what this prints before you run it:

    Matrix a = /* your first matrix  */;
    Matrix b = /* your second matrix */;
    
    Set<Matrix> hashed = new HashSet<>(List.of(a, b));
    Set<Matrix> sorted = new TreeSet<>(List.of(a, b));
    
    System.out.println(hashed.size() + " " + sorted.size());

    Write your prediction in a comment above the code, then run it. Leave the comment in even if the prediction was wrong — especially then.

    [[1, 0], [0, 0]] and [[0, 1], [0, 0]] are one pair that works, and there are many others.

  2. Make compareTo agree with equals. Change compareTo so that it returns zero only for matrices that are also equals to each other, and re-run task 6.

    Compare the magnitudes first, as now. When those are equal, keep going: compare the shapes, and then the numbers, until you find something that differs. Two matrices that are equals will run out of things to compare, and that is when you return zero.

    The javadoc for Comparable calls this being consistent with equals, and says it is “strongly recommended” rather than required. Task 6 shows you what the recommendation is protecting.

Part 4 — the same four ideas, on a different class

Everything above happened to Matrix. None of it was about matrices. Part 4 is the proof: the same four moves — a natural order, a comparator, equals, and the place where the two contracts meet — on Fraction, which is already in your repo and is finished.

  1. Make Fraction implement Comparable<Fraction>, ordering fractions by value, smallest first. -1/4 comes before 1/3, which comes before 1/2.

    Do not subtract one fraction from the other and cast to int. It is the task 1 mistake wearing a different coat, and it is worse here: every fraction strictly between -1 and 1 subtracts to something that casts to 0, so Arrays.sort decides most of your array is already in order and leaves it alone. Run it once if you want to see what that looks like.

    Compare a/b against c/d by comparing a*d against c*b instead. Two things to get right. First, multiplying two ints does not give you an int when the numbers get large — Long.compare and a cast fix that. Second, cross-multiplying like this is only valid when the denominators are positive, because multiplying an inequality by a negative number flips it. Open the constructor and find out whether they are. Do not assume either answer.

  2. Write src/SortFractions.java with a main that builds an array of at least four fractions, prints it, calls Arrays.sort, and prints it again. Include at least one negative fraction.

  3. Give Fraction a getNumerator() and a getDenominator(), both returning int.

    num and den are private, and task 11 needs to read them from a different class. A comparator is not a friend of the class it sorts — it sees the public surface and nothing else. Two accessors are the whole fix, and they hand out copies of two ints, which is not a leak.

  4. Write src/ByDenominator.java, a Comparator<Fraction> that orders fractions by denominator alone, smallest first.

    1/3 and 2/3 tie under it, and so do 1/2 and -1/2. That is the same situation BySize was in back in task 5.

  5. Give Fraction an equals and a hashCode. Two fractions are equal when they have the same numerator and the same denominator.

    Same rules as Matrix: equals takes an Object, and anything that is equals must have the same hashCode.

  6. Run the task 6 experiment again, on fractions. In the same main, write down what you expect before you run it:

    Fraction p = new Fraction(2, 4);
    Fraction q = new Fraction(1, 2);
    
    Set<Fraction> hashed = new HashSet<>(List.of(p, q));
    Set<Fraction> sorted = new TreeSet<>(List.of(p, q));
    
    System.out.println(hashed.size() + " " + sorted.size());

    Then, in a comment underneath, answer this in two or three sentences: Matrix needed task 7 to make compareTo and equals agree. Fraction did not. Why not?

    The answer is in what each order is built on. magnitude() is one number computed from a matrix; a fraction’s value is the fraction. Say what follows from that.

Handing in

Commit and push. The last commit before the deadline is what gets marked.

Every push is graded, one check at a time, and the result lands on your feedback pull request — so push early and read what failed, rather than saving it all for the last night.

A student picked at random presents this one in class the following Wednesday.