La Función sorted En Kotlin

En este tutorial verás cómo usar a la función sorted en Kotlin con el fin de ordenar los elementos de una colección de acuerdo a su orden natural o un criterio particular que desees proveer. Esto implica la presentación de ejemplos para las variaciones sortedDescending(), sortedBy(), sortedByDescending() y sortedWith().

Orden Natural

El orden natural es el que se establece para aquellos tipos que implementan a la interfaz Comparable. La mayoría de tipos básicos de Kotlin tienen orden natural, por lo que son de gran utilidad al momento de usar funciones de ordenamiento.

A continuación veremos dos funciones que te permiten aplicar este concepto.

Función sorted()

La función de extensión sorted() retorna una lista ordenada (en forma ascendente) de los elementos que se encuentran en la colección invocadora. En su definición de sintaxis verás que el parámetro de tipo tiene la restricción para que implemente a Comparable<T>.

// sorted() en Arreglos
fun <T : Comparable<T>> Array<out T>.sorted(): List<T>

// sorted() en Iterables
fun <T : Comparable<T>> Iterable<T>.sorted(): List<T>

Considera el arreglo de números enteros numbers = [8, 1, 2, -3, 0] y la necesidad de ordenar a sus elementos en orden ascendente según la recta numérica (orden natural). Aplicando la función sorted() en Kotlin tendríamos el siguiente código:

fun main() {
    val numbers = arrayOf(8, 1, 2, -3, 0)
    val sortedNumbers = numbers.sorted()
    println(sortedNumbers)
}

Salida:

[-3, 0, 1, 2, 8]

El resultado de aplicar sorted() al arreglo de números será la lista entera de solo lectura sortedNumbers con un orden acorde a la teoría matemática.

Función sortedDescending()

Como el nombre lo sugiere, sortedDescending() hace exactamente lo mismo que sorted(), solo que con un orden descendente en los elementos de la colección invocadora.

// sortedDescending() en Arreglos
fun <T : Comparable<T>> Array<out T>.sortedDescending(): List<T>

// sortedDescending() en Iterables
fun <T : Comparable<T>> Iterable<T>.sortedDescending(): List<T>

Tomemos como ejemplo una lista con los nombres de personas. La idea es ordenar de forma descendente la lista en orden alfabético.

fun main() {
    val names = listOf("Bruno", "Maricela", "Victor", "Ana", "Vilma")
    val namesInDescOrder = names.sortedDescending()
    println(namesInDescOrder)
}

Salida:

[Vilma, Victor, Maricela, Bruno, Ana]

El orden natural para los strings y caracteres usa sus códigos numéricos. Es por esta razón que Vilma es mayor a Victor, ya que el tercer carácter define la superioridad ('l' > 'c').

Orden Personalizado

El orden personalizado se refiere a determinar particularmente la preponderancia de un elemento de cualquier tipo frente a otro. Esto quiere decir que cuando el orden natural no satisface tus necesidades, entonces defines por tu cuenta el criterio de ordenamiento. O si deseas establecer orden para objetos no comparables.

Las siguientes son funciones que materializan este mecanismo.

Función sortedBy()

La función sortedBy() ordena en forma ascendente los elementos de una colección, basado en el orden natural del selector pasado como parámetro. Esta retorna en una lista de solo lectura con el ordenamiento final.

// sortedBy() en Arreglos
inline fun <T, R : Comparable<R>> Array<out T>.sortedBy(
    crossinline selector: (T) -> R?
): List<T>

// sortedBy() en Iterables
inline fun <T, R : Comparable<R>> Iterable<T>.sortedBy(
    crossinline selector: (T) -> R?
): List<T>

Por ejemplo, supongamos que tenemos el diseño de una clase que representa el salario que gana un trabajador.

data class Employee(val name: String, val salary: Int)

Dada una lista de varios empleados de la compañía, se requiere imprimir en orden ascendente los nombres de los empleados según su salario.

fun main() {   
    val employees = listOf(
        Employee("Carolina", 2000),
        Employee("Shirley", 1500),
        Employee("Carlos", 1250),
        Employee("Jill", 4200),
    )
    val sortedEmployees = employees
        .sortedBy { it.salary }
        .map { it.name }

    println(sortedEmployees)
}

Salida:

[Carlos, Shirley, Carolina, Jill]

En este ejemplo la función sortedBy() es de gran utilidad, ya que la clase Employee no implementa Comparable. Es por eso que la función lambda que pasamos como selector permite mapear a la propiedad salary, con el fin de realizar el ordenamiento numérico.

Seguido a sortedBy(), usamos a map() para obtener solo los nombres de los empleados. Al final imprimimos a sortedEmployees y conseguimos el ordenamiento esperado, Carlos de primero al ganar menos y Jill al final por ganar más.

Función sortedByDescending()

sortedByDescending() es la versión de sortedBy() para en un ordenamiento descendente. Pasa como parámetro el selector que mapee a los elementos de una colección a instancias comparables y así retornar una lista de solo lectura.

// sortedByDescending() en Arreglos
inline fun <T, R : Comparable<R>> Array<out T>.sortedByDescending(
    crossinline selector: (T) -> R?
): List<T>

// sortedByDescending() en Iterables
inline fun <T, R : Comparable<R>> Iterable<T>.sortedByDescending(
    crossinline selector: (T) -> R?
): List<T>

Como ejemplo usemos una caso donde no deseamos usar el orden natural establecido para los strings, si no que deseamos hacerlo por su tamaño.

fun main() {
    val strings = listOf("xxx", "xy", "y", "xx", "yyxy")
    val sortedStrings = strings.sortedByDescending { it.length }
    println(sortedStrings)
}

Salida:

[yyxy, xxx, xy, xx, y]

Recuerda que el cuerpo del parámetro selector es anulable, por lo que puedes pasar null y conservar el orden actual.

val sortedStrings = strings.sortedByDescending { null }

Función sortedWith()

La función sortedWith() retorna en una lista ordenada de acuerdo a la instancia Comparator pasada como argumento.

// sortedWith() con Arreglos
fun <T> Array<out T>.sortedWith(
    comparator: Comparator<in T>
): List<T>

// sortedWith() con Iterables
fun <T> Iterable<T>.sortedWith(
    comparator: Comparator<in T>
): List<T>

Supongamos que ahora deseamos comparar otra lista de empleados, pero esta vez usando las dos propiedades de nombre y salario. Para ello necesitamos crear un comparador que use ambos elementos para luego pasarlo a sortedWith():

fun main() {   
    val moreEmployees = listOf(
        Employee("Carolina", 2000),
        Employee("Shirley", 1500),
        Employee("Carlos", 1250),
        Employee("Carolina", 3000),
        Employee("Jill", 4200),
        Employee("Carlos", 5500),
    )
    val nameAndSalaryComparator = compareBy(Employee::name, Employee::salary)
    val result = moreEmployees.sortedWith(nameAndSalaryComparator)
    result.forEach(::println)
}

Salida:

Employee(name=Carlos, salary=1250)
Employee(name=Carlos, salary=5500)
Employee(name=Carolina, salary=2000)
Employee(name=Carolina, salary=3000)
Employee(name=Jill, salary=4200)
Employee(name=Shirley, salary=1500)

Una forma fácil de crear una comparador basado en selectores es la función compareBy(). Como ves, pasamos dos referencias para las propiedades como argumentos.

Luego se invoca a sortedWith() sobre moreEmployees con el comparador y finalmente imprimimos cada empleado con forEach().

El resultado impreso nos muestra cómo se realiza el ordenamiento alfabético por el nombre del empleado y al mismo tiempo se considera mayor a aquellos elementos que tengan un salario más alto.

Por esta razón el empleado Carlos con salario de 5500 está en la posición 2 y Carolina con 3000 de salario va después de aquella que solo gana 2000.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!