Start learning free

LESSON 199 OF 348 · SORTING

Sorting in Kotlin: sort, sortWith and Comparators

Lesson Read the full explanation

In order to sort a static array we can use the sort() function which sorts the elements of a given array in ascending order (default), descending order or user-defined order.

Similarly, if we want to sort a mutable list, we can use sort() which works the same way.

Important: Sorting in reverse or with a specified order using a comparator is possible only if we use objects and not primitives. This means that it will work for Array<Int> array, but not on IntArray, at least not as easy as we think.

Syntax:

Let's see some examples below. Sorting an array of integers in ascending order

val numbers = intArrayOf(11, 3, 7, 5, 2)

numbers.sort() // numbers becomes [2, 3, 5, 7, 11]

Sorting a mutable list of Ints in ascending order

val arr = mutableListOf(11, 3, 7, 5, 2)

arr.sortDescending() // numbers becomes [2, 3, 5, 7, 11]

Sorting an array of integers in descending order

val numbers = intArrayOf(11, 3, 7, 5, 2)

numbers.sortWith(Comparator.reverseOrder()) // compile error!

Notice that we have an IntArray, so we won't be able to sort it in reverse directly! We need to transform it into an Array<Int> as below:

val numbers = intArrayOf(11, 3, 7, 5, 2)

// transform it to Array<Int>
val arr = numbers.toTypedArray()

// Now we can sort it
arr.sortWith(Comparator.reverseOrder()) //arr becomes [11, 7, 5, 3, 2]

Sorting an array of Strings in descending order

val names = arrayOf("Mircea", "Andy", "Xavier", "Michael", "Christina")

names.sortWith(Comparator.reverseOrder()) // --> ['Xavier', 'Mircea', 'Michael', 'Christina', 'Andy']

Sort with custom function (comparator):

The sortWith() function supports a basic comparison function that we can use to specify how we want to sort:

fun compare(a: Int, b: Int): Int {
    return a - b
}

This can be written as a lambda function (nameless function we can easily pass to sort()) as follows:

{ a, b ->
    a - b
}

If the function just returns one thing, we can write it as follows:

{ a, b -> a - b }

When the sort() function compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value:

  • If the result is negative a is sorted before b.
  • If the result is positive b is sorted before a.
  • If the result is 0 no changes are done with the sort order of the two values.

All we need to is using the compare function (as a lambda function) inside the sort() method:

val numbers = arrayOf(3, 23, 12)

// using the short version of the lambda function
numbers.sortWith { a, b -> a - b } // --> 3, 12, 23

To demonstrate a basic example, let’s assume the requirement for ordering a specific list is the length of the strings in the list, shortest to longest. We will be using the length property of each string to compare them.

val words = arrayOf("banana", "pie", "Washington", "book")

words.sortWith { a, b -> a.length - b.length } // --> ['pie', 'book', 'banana', 'Washington']

Example: sorting array of pairs

Let's say we want to sort an array, where each element is another array consisting of two numbers.

This time we will be using Array<IntArray> since each IntArray element is treated as an object so it can be sorted with a compare function

val pairs = arrayOf(
    intArrayOf(1, 5),
    intArrayOf(12, 5),
    intArrayOf(10, 1),
    intArrayOf(1, 3)
)

pairs.sortWith { a, b ->
    // in case the first values of each pair are equal,
    // compare them using their second values
    if (a[0] == b[0]) {
        a[1] - b[1]
    } else {
        a[0] - b[0]
    }
}
// pairs --> [[1, 3], [1, 5], [10, 1], [12, 5]]

Time Complexity:

Sorting an array via sort() takes O(n * log n * C) time, where C is the time taken to compare two elements from the array.

It takes O(1) (constant time) to compare two numbers, therefore sorting an array of numbers takes O(n log n).

However, it takes O(L) to compare two strings, therefore sorting an array of strings takes O(n * log n * L), where L is the average length of the strings.


Space Complexity:

The sort() method sorts the elements of an array in place (transforms the array directly, using no auxiliary data structure), so the extra space used for this is O(1).


Assignment
Follow the Coding Tutorial and let's sort some arrays.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    KOTLIN · SORTING

    Sorting in Kotlin, explained

    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() and sortWith(...) sort in place and return nothing useful. They work on arrays and on MutableList.
    • sorted(), sortedDescending() and sortedWith(...) leave the original alone and return a new List.
    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: a goes before b;
    • positive: b goes before a;
    • 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.

    1. Numbers, descending. Declare numbers as an IntArray with intArrayOf(2, 1, 3, 10) and call sortDescending(). It sorts in place, so numbers ends up as [10, 3, 2, 1].
    2. Pairs, by first value ascending and then second value descending. Declare coordinates with arrayOf holding three IntArray pairs. In the comparator, when the first values are equal, return b[1] - a[1], which reverses the second values. Otherwise return a[0] - b[0]. The result is [2, 3], [3, 5], [3, 2].
    3. Names, ignoring case. Compare a.lowercase() with b.lowercase() using compareTo. Without the lowercasing, "john" would come last. With it, the order is Andy, 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() and sortWith() change the collection; sorted(), sortedDescending() and sortedWith() return a new list.
    • IntArray has no sortWith. Use sortDescending(), or convert with toTypedArray().
    • A comparator returns a negative number, zero or a positive number, and the sort is stable.
    • Prefer compareTo to subtraction when values can be large.
    • Strings sort by character code unless you lowercase them or use a case-insensitive comparator.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free