What sorting means in Kotlin
Sorting puts the elements of a collection in a chosen order. Many interview problems get much easier once the data is sorted, because equal values end up next to each other, the smallest values come first, and pairs of close values sit side by side. Kotlin gives you ready-made sorting functions, so you rarely write a sorting algorithm yourself. What you do need to know is which function to call on which type, and how to describe an order of your own.
The first thing to decide is whether you want to change the collection or get a sorted copy. Kotlin names the two families differently:
sort(),sortDescending()andsortWith(...)sort in place and return nothing useful. They work on arrays and onMutableList.sorted(),sortedDescending()andsortedWith(...)leave the original alone and return a newList.
fun main() {
val a = intArrayOf(5, 1, 4)
val copy = a.sorted() // a new List<Int>
println("$copy ${a.toList()}") // [1, 4, 5] [5, 1, 4]
a.sort() // changes a itself
println(a.toList()) // [1, 4, 5]
}
A common slip is writing sortedDescending() and then reading the original array: it is unchanged, because the sorted result was thrown away.
Sorting numbers and strings
For an IntArray, sort() gives ascending order and sortDescending() gives descending order. Both are in place, so declaring the array with val is fine: val fixes which array the name refers to, not what is inside it.
val numbers = intArrayOf(2, 1, 3, 10)
numbers.sortDescending()
println(numbers.toList()) // [10, 3, 2, 1]
Mutable lists work the same way. Calling sortDescending() on mutableListOf(11, 3, 7, 5, 2) leaves [11, 7, 5, 3, 2], with the largest value first.
Strings compare by character code, so every uppercase letter sorts before every lowercase one:
val names = arrayOf("Mircea", "john", "Andy")
names.sort()
println(names.toList()) // [Andy, Mircea, john]
IntArray versus Array<Int>
Kotlin has two array types for integers, and the difference matters for sorting. IntArray stores plain primitive int values. Array<Int> stores boxed objects. The comparator-based functions, sortWith and sortedWith, exist for Array<T> but not for IntArray. This code does not compile:
val numbers = intArrayOf(11, 3, 7, 5, 2)
numbers.sortWith(Comparator.reverseOrder())
The compiler reports unresolved reference 'sortWith'. For a plain descending sort of an IntArray you do not need a comparator, because sortDescending() exists. If you really need a custom order, convert with toTypedArray(), which gives an Array<Int>:
val arr = intArrayOf(11, 3, 7, 5, 2).toTypedArray()
arr.sortWith(Comparator.reverseOrder())
println(arr.toList()) // [11, 7, 5, 3, 2]
An Array<IntArray> is different again: each element is an IntArray object, so sortWith is available on the outer array. That is how you sort pairs.
Custom orders with a comparator
A comparator takes two elements, a and b, and returns an Int:
- negative:
agoes beforeb; - positive:
bgoes beforea; - zero: the two are equal for this ordering, and their relative order is kept (the sort is stable).
You can pass it as a lambda. The longer form wraps it in Comparator { a, b -> ... }, which is the form this lesson uses:
val words = arrayOf("banana", "pie", "Washington", "book")
words.sortWith { a, b -> a.length - b.length }
println(words.toList()) // [pie, book, banana, Washington]
The lambda's last expression is its result, so there is no return. In Kotlin if/else is an expression too, which makes multi-key comparisons short: the result of whichever branch runs becomes the result of the lambda.
Subtraction can overflow
Writing a - b is common, but Int is 32 bits and overflow wraps silently. With values near the limits the sign comes out wrong:
val big = arrayOf(Int.MAX_VALUE, -5, 3)
big.sortWith { x, y -> x - y }
println(big.toList()) // [2147483647, -5, 3] (wrong)
val ok = arrayOf(Int.MAX_VALUE, -5, 3)
ok.sortWith { x, y -> x.compareTo(y) }
println(ok.toList()) // [-5, 3, 2147483647]
Use compareTo whenever the values can be large. Subtraction is fine when you know the numbers are small.
The exercise, step by step
The lesson asks for three sorts.
- Numbers, descending. Declare
numbersas anIntArraywithintArrayOf(2, 1, 3, 10)and callsortDescending(). It sorts in place, sonumbersends up as[10, 3, 2, 1]. - Pairs, by first value ascending and then second value descending. Declare
coordinateswitharrayOfholding threeIntArraypairs. In the comparator, when the first values are equal, returnb[1] - a[1], which reverses the second values. Otherwise returna[0] - b[0]. The result is[2, 3],[3, 5],[3, 2]. - Names, ignoring case. Compare
a.lowercase()withb.lowercase()usingcompareTo. Without the lowercasing,"john"would come last. With it, the order isAndy,john,Mircea.
val numbers = intArrayOf(2, 1, 3, 10)
numbers.sortDescending()
val coordinates = arrayOf(
intArrayOf(3, 2),
intArrayOf(2, 3),
intArrayOf(3, 5)
)
coordinates.sortWith(Comparator { a, b ->
if (a[0] == b[0]) {
b[1] - a[1]
} else {
a[0] - b[0]
}
})
val names = arrayOf("Mircea", "john", "Andy")
names.sortWith(Comparator { a, b -> a.lowercase().compareTo(b.lowercase()) })
Two shortcuts give the same results. compareBy({ it[0] }, { -it[1] }) builds the pair comparator from keys, and String.CASE_INSENSITIVE_ORDER is a ready-made case-insensitive comparator.
Cost of sorting
Sorting n elements takes O(n log n) comparisons. If one comparison costs C, the total is O(n log n * C). Comparing two numbers is constant time, so sorting numbers is O(n log n). Comparing two strings costs up to their length L, so sorting strings is O(n log n * L).
The in-place functions on an IntArray need no copy of the data, but the sorting routine itself may use some temporary memory, so treat "O(1) extra space" as a rough description for primitives. The sorted... family always allocates a new list of size n.
Key takeaways
sort(),sortDescending()andsortWith()change the collection;sorted(),sortedDescending()andsortedWith()return a new list.IntArrayhas nosortWith. UsesortDescending(), or convert withtoTypedArray().- A comparator returns a negative number, zero or a positive number, and the sort is stable.
- Prefer
compareToto subtraction when values can be large. - Strings sort by character code unless you lowercase them or use a case-insensitive comparator.