Operaciones con colecciones VII: Ordenando los elementos

Operaciones con colecciones VII: Ordenando los elementos

En ciertos tipos de colecciones, el orden de sus elementos es un aspecto importante. Además, hay que tener en cuenta que dos listas con los mismos elementos no son iguales si sus elementos están ordenados de manera diferente.

En Kotlin, los objetos se pueden ordenar siguiendo distintos criterios. Primero, hay un orden natural para los objetos que heredan la interfaz Comparable y que se utiliza para clasificar los objetos del mismo tipo cuando no se especifica ningún otro criterio. Por ejemplo, los tipos numéricos utilizan el orden numérico tradicional (1 es mayor que 0 y -3.4f es mayor que -5f) y Char y String usan el orden lexicográfico ('b' es mayor que 'a' y 'mundo' es más mayor que 'hola').

Además, el usuario puede definir un orden natural para otro tipo haciendo que herede de la interfaz Comparable, lo que requiere implementar la función compareTo(). Esta función debe tomar otro objeto del mismo tipo como argumento y devuelve un valor entero que muestra qué objeto es mayor: si es positivo el objeto receptor es mayor, si es negativo el argumento es mayor y si es cero ambos objetos son iguales.

Un ejemplo de una clase que se puede usar para ordenar versiones:
class Version(private val primero: Int, private val segundo: Int) : Comparable<Version> {
    override fun compareTo(other: Version): Int {
        return when {
            this.primero != other.primero -> this.primero - other.primero
            this.segundo != other.segundo -> this.segundo - other.segundo
            else -> 0
        }
    }
}

fun main() {
    println(Version(1, 2) > Version(1, 3)) // false
    println(Version(2, 0) > Version(1, 5)) // true
}

Podemos ordenar instancias de cualquier tipo de la forma que nos interese, pudiendo definir un orden para objetos no comparables o bien definir un orden no natural para un tipo comparable. Para definir un orden determinado para un tipo, debemos crear un Comparator para él. Comparator contiene la función compare() que toma dos instancias de una clase y devuelve un resultado entero que se interpreta como hemos visto antes en la función compareTo():
fun main() {
    val ordenLongitud = Comparator { str1: String, str2: String -> str1.length - str2.length }
    println(listOf("aaa", "bb", "c").sortedWith(ordenLongitud))
    // [c, bb, aaa]
}

En este ejemplo estamos organizando palabras por su longitud en lugar del orden lexicográfico natural. Podemos definir un comparador de manera más concisa con la función compareBy(), que toma una función lambda que produce un valor comparable de una instancia y define el orden personalizado como el orden natural de los valores producidos, por lo que podemos escribir el código anterior así:
println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))

El paquete de colecciones de Kotlin proporciona unas funciones específicas para ordenar los elementos de las colecciones por su orden natural, por criterios personalizados o de manera aleatoria. Estas funciones se aplican a las colecciones de solo lectura y devuelven como resultado una nueva colección que contiene los mismos elementos que la colección original pero presentados en el orden requerido.

Las funciones básicas sorted() y sortedDescending() devuelven los elementos de una colección ordenados en secuencia ascendente y descendente según su orden natural. Estas funciones se aplican a las colecciones de elementos comparables.
val numeros = listOf("uno", "dos", "tres", "cuatro")
println("En orden ascendente: ${numeros.sorted()}")
println("En orden descendente: ${numeros.sortedDescending()}")

Para utilizar criterios personalizados u ordenar objetos no comparables podemos usar las funciones sortedBy() y sortedByDescending() que toman una función que asigna los elementos de la colección a valores de Comparable y ordena la colección en el orden natural de esos valores.
val numeros = listOf("uno", "dos", "tres", "cuatro", "cinco")

val ordenLongitud = numeros.sortedBy { it.length }
println("Orden ascendente por longitud: $ordenLongitud")

val ultimaLetra = numeros.sortedByDescending { it.last() }
println("Orden descendente por última letra: $ultimaLetra")

Para definir un criterio personalizado para ordenar una colección, podemos proporcionar nuestro propio Comparator llamando a la función sortedWith():
val numeros = listOf("uno", "dos", "tres", "cuatro", "cinco")
println("Orden ascendente por longitud: ${numeros.sortedWith(compareBy { it.length })}")

Podemos recuperar la colección en orden inverso utilizando la función reversed():
println(numeros.reversed())

Hay que tener en cuenta que reversed() devuelve una nueva colección con copias de los elementos y, por tanto, si más adelante cambia la colección original, esto no afectará a los resultados obtenidos previamente con reversed(). En cambio la función asReversed() devuelve una vista invertida de la misma instancia de la colección, por lo que es preferible frente a reversed() al requerir menos recursos siempre que la lista original no vaya a cambiar o sea inmutable.
val numeros = mutableListOf("uno", "dos", "tres", "cuatro")

val ordenInverso = numeros.reversed()
val ordenInversoAs = numeros.asReversed()
println(ordenInverso)   // [cuatro, tres, dos, uno]
println(ordenInversoAs) // [cuatro, tres, dos, uno]

numeros.add("cinco")
println(ordenInverso)   // [cuatro, tres, dos, uno]
println(ordenInversoAs) // [cinco, cuatro, tres, dos, uno]

Por último, la función shuffled() devuelve una nueva lista que contiene los elementos de la colección mezclados en un orden aleatorio, pudiéndose llamar sin argumentos o con un objeto tipo Random:
println(numeros.shuffled())

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

I/O: entrada y salida de datos en consola

Lectura y escritura de archivos