In previous lessons, we have learned to order collections based on the methods defined in the Comparator interface and the compareTo() method following the intrinsic natural order of elements.
In this topic, we will see how to customize the order of a collection based on our needs or tasks with the help of special methods defined in Kotlin.
Custom order
To perform custom sorting of the elements in a collection and order the elements of any type in a way you like, Kotlin offers the following functions: sortedBy() and sortedByDescending().
How do they work? They take a lambda as the selector function, which maps the element or some properties of the element into comparable attributes. As a result, it returns an ordered collection following the identified natural order. sortedBy() returns a collection where the elements are sorted in ascending order, and sortedByDescending() returns a collection in descending order.
fun main() {
val words = listOf("peter", "anne", "michael", "caroline")
// sorted by word length
println(words.sortedBy { it.length }) // [anne, peter, michael, caroline]
println(words.sortedByDescending { it.length }) // [caroline, michael, peter, anne]
// sorted by first letter
println(words.sortedBy { it.first() }) // [anne, caroline, michael, peter]
println(words.sortedByDescending { it.first() }) // [peter, michael, caroline, anne]
// sorted by the last letter
println(words.sortedBy { it.last() }) // [anne, caroline, michael, peter]
println(words.sortedByDescending { it.last() }) // [peter, michael, anne, caroline]
}Define new order
Sometimes, natural order is not enough, and you need to implement a more specific order to work with the elements of a collection; for example, you can implement a custom order for non-comparable elements or properties.
We can define custom sorting using the Comparator interface. The Comparator interfaces helps us to define the compare() method, which returns the integer result of the comparison between two values: a zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second. In this way, you can set the order that you need in a collection. We can define the compare() function using a lambda, for example, to return a positive number if the first element must precede the second, if a date must precede another date, if a string must precede another string according to the string length, and so on.
Now we can use the sortedWith() function to return a list of all elements sorted according to the specified comparator.
fun main() {
val words = listOf("peter", "anne", "michael", "caroline")
// define a comparator for the list of words
val wordsLengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
// Ordered list according to the comparator
println(words.sortedWith(wordsLengthComparator)) // [anne, peter, michael, caroline]
// another comparator using the middle letter
val middleLetterComparator =
Comparator { str1: String, str2: String -> str1[str1.length / 2] - str2[str2.length / 2] }
// Ordered list according to the comparator
println(words.sortedWith(middleLetterComparator)) // [michael, caroline, anne, peter]
}Improving your custom order
Another alternative way to accomplish this task is to use the compare() function, which allows us to define on the fly the comparator that we need using a lambda function to customize the sorting of the elements in a collection with sortedWith().
fun main() {
val words = listOf("peter", "anne", "michael", "caroline")
// ordering by length
println(words.sortedWith(compareBy { it.length })) // [anne, peter, michael, caroline]
// ordering by middle letter
println(words.sortedWith(compareBy { it[it.length / 2] })) // [michael, caroline, anne, peter]
}
Finally, you can combine your custom ordering method with the reversed() function to obtain the inverse form of the defined order.
fun main() {
val words = listOf("peter", "anne", "michael", "caroline")
// ordering by length reversed
println(words.sortedWith(compareBy { it.length }).reversed()) // [caroline, michael, peter, anne]
// ordering by middle letter reversed
println(words.sortedWith(compareBy { it[it.length / 2] }).reversed()) // [peter, anne, caroline, michael]
}Conclusion
Throughout this topic, we have shown different techniques to customize the ordering of elements in a collection. Now you will be able to adapt ordering to the requirements of your problem based on a specific natural order or setting your own comparison criteria.
It is time to solve some problems to check what you have learned about custom sorting. Let's go!