Sorting by your own rule
Collections.sort puts numbers from small to large and text from A to Z. Often you need another order: the highest score first, the shortest word first, or names ordered by their last letter. For that, give the list's sort method a lambda.
A lambda is a small function written right where you need it, with no name. The lambda (a, b) -> a - b gets two values, a and b, and returns a - b. The sort method calls it on two items at a time, and the number it returns says which one goes first:
- A negative number puts
afirst. - A positive number puts
bfirst. - Zero means they are equal.
Rules you will use often:
- Low to high:
(a, b) -> a - b - High to low:
(a, b) -> b - a - Shortest first:
(a, b) -> a.length() - b.length()
For plain numbers or text, list.sort(Comparator.reverseOrder()) also sorts from high to low.
Two rules
When one rule isn't enough, the lambda needs a body in braces. Check the first rule. Use the second one only on a tie.
List<Integer> nums = new ArrayList<>(List.of(25, 13, 5, 41, 33));
nums.sort((a, b) -> {
if (a % 10 != b % 10) {
return a % 10 - b % 10;
}
return a - b;
});
System.out.println(nums);
[41, 13, 33, 5, 25]
The first rule compares the last digits, a % 10 and b % 10. Only when they are the same does the lambda reach return a - b, which puts the smaller number first.
Comparing text and characters
Strings can't be subtracted, so use compareTo. x.compareTo(y) is negative when x comes before y from A to Z, positive when it comes after, and 0 when they are equal.
A char is stored as a number, so two chars can be subtracted. 'x' - 'a' is 23, and lastA - lastB is negative when lastA comes earlier in the alphabet. The last letter of a string s is s.charAt(s.length() - 1).
Copies and fixed lists
The sort method changes the list itself. To keep the original, sort a copy made with new ArrayList<>(words):
List<String> words = new ArrayList<>(List.of("pear", "fig", "banana", "kiwi"));
List<String> copy = new ArrayList<>(words);
copy.sort((a, b) -> a.length() - b.length());
System.out.println(copy + " " + words);
[fig, pear, kiwi, banana] [pear, fig, banana, kiwi]
Items that compare as equal keep their original order, because the sort is stable. Note also that List.of(...) alone makes a list that can't change, and calling sort on it throws UnsupportedOperationException. That is why the lesson wraps it in new ArrayList<>(...).
The lesson's task
A pet shop has prices and names. Sort prices from high to low, and sort names by their last letter, with names that share a last letter going from A to Z. The loops that print both are already in the starter code.
The prices need one rule, b - a. The names need two. Inside the lambda, keep the last letter of each name in a char. If the two chars differ, return lastA - lastB. Otherwise return a.compareTo(b), which puts Max before Rex.
class Solution {
public void mainFunction() {
List<Integer> prices = new ArrayList<>(List.of(12, 5, 30, 8));
List<String> names = new ArrayList<>(List.of("Rex", "Luna", "Bella", "Max", "Bo", "Coco"));
prices.sort((a, b) -> b - a);
for (int price : prices) {
System.out.println(price);
}
names.sort((a, b) -> {
char lastA = a.charAt(a.length() - 1);
char lastB = b.charAt(b.length() - 1);
if (lastA != lastB) {
return lastA - lastB;
}
return a.compareTo(b);
});
for (String name : names) {
System.out.println(name);
}
}
}
It prints:
30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
Common mistakes
- Swapping
aandb.a - bsorts low to high,b - ahigh to low. - Dropping the second rule. With only the last-letter rule, ties keep their original order and the names come out as
[Luna, Bella, Bo, Coco, Rex, Max]. Rex precedes Max only because it was first in the list. - Subtracting huge numbers.
b - ais fine for small prices, butintsubtraction can overflow. WithInteger.MIN_VALUEand 5,(a, b) -> b - aleaves the list as[-2147483648, 5], whileInteger.compare(b, a)gives the right[5, -2147483648]. For values that can be that large, useInteger.compare. - Sorting a list made with
List.of. It can't change, sosortthrows an exception.
Key takeaways
- Give
list.sorta lambda ofaandb; a negative result putsafirst. - Use
a - bfor low to high andb - afor high to low. - For two rules, return the first rule's result when it isn't a tie, and fall back to the second rule otherwise.
- Compare strings with
compareToand chars by subtracting them. sortchanges the list; copy it first to keep the original.
