La Función maxOrNull En Kotlin

En este tutorial examinarás la definición y uso de la función maxOrNull en Kotlin para encontrar el elemento más valor grande de una colección. Se te proveerán ejemplos para ilustrar el propósito de la función y sus variaciones maxByOrNull(), maxWithOrNull() y maxOf().

Función maxOrNull()

La función maxOrNull() en Kotlin te permite determinar el elemento con el valor máximo de una colección. Si la colección no tiene elementos, entonces retornará null.

Ya que tenemos varios tipos de colecciones, existe una función de extensión asociada a cada uno como se muestra en las siguientes sintaxis:

// maxOrNull() en Arrays
fun Array<out Double>.maxOrNull(): Double?
fun Array<out Float>.maxOrNull(): Float?
fun <T : Comparable<T>> Array<out T>.maxOrNull(): T?

// maxOrNull() en Iterables
fun Iterable<Double>.maxOrNull(): Double?
fun Iterable<Float>.maxOrNull(): Float?
fun <T : Comparable<T>> Iterable<T>.maxOrNull(): T?

// maxOrNull() en Strings
fun CharSequence.maxOrNull(): Char?

Considera la cadena de texto «Develou». Si invocaras la función maxOrNull() sobre ella ¿cuál sería el resultado?:

Ejemplo de la función maxOrNull en Kotlin

Evidentemente será el carácter 'v', ya que su código numérico es mayor que el resto de letras. La representación de este ejemplo en Kotlin es la siguiente:

fun main() {
    val text = "Develou"
    val max = text.maxOrNull()
    println("Caracter con el código más grande: '$max'") // 'v'
}

Recuerda que el tipo de retorno es anulable, por lo que si la colección está vacía tendrás como resultado null:

fun main() {
    val emptyList = emptyList<Int>()
    println(emptyList.maxOrNull()) // null
}

Función maxByOrNull()

La función maxByOrNull() hace exactamente lo mismo que maxOrNull() solo que toma como parámetro una función selectora. El objetivo de este parámetro es proveer el valor por el cual cada elemento será considerado mayor a otro. Además debe implementar a la interfaz Comparable.

Por ejemplo, supón que dado tres puntos P1 (-1, 2), P2 (2, 1) y P3 (2, 2) en el plano coordenado XY, necesitas determinar cual tiene la mayor distancia hasta el origen O (0, 0).

import kotlin.math.hypot

fun main() {
    val points = listOf(-1 to 2, 2 to 1, 2 to 2)

    val farthestPoint = points.maxByOrNull(::calculateDistanceFromOrigin)
    println("Punto más alejado: $farthestPoint")
}

private fun calculateDistanceFromOrigin(point: Pair<Int, Int>): Double {
    val x = point.first.toDouble()
    val y = point.second.toDouble()
    return hypot(x, y)
}

Salida:

Punto más alejado: (2, 2)

Como ves, el punto más alejado se encuentra usando maxByOrNull() con la referencia de la función calculateDistanceFromOrigin() como selector. Esta recibe un par de enteros y en su interior hace una conversión a Double para aplicar la función hypot(), la cual calcula la raíz de la suma de cuadrados de ambas coordenadas sqrt(x2+y2).

Función maxWithOrNull()

Retorna el primero elemento con el valor más grande de acuerdo al parámetro Comparator que hayas proveído.

Retomemos el mismo diseño de la clase para representar ríos y su largo en kilómetros que vimos en minOrNull():

data class River(val name: String, val length: Int)

Si quisiéramos encontrar el río más grande de una lista aplicamos a la función maxWithOrNull() de la siguiente forma:

fun main() {

    val rivers = listOf(
        River("Nilo", 6690),
        River("Mississippi", 6270),
        River("Amazonas", 6387),
        River("Negro", 2250)
    )
    val longestRiver = rivers.maxWithOrNull(compareBy { it.length })
    println("El río más largo: $longestRiver")
}

Salida:

El río más largo: River(name=Nilo, length=6690)

Pasamos como instancia un comparador creado con la función compareBy(), la cual recibe una lambda que selecciona a la propiedad River.length como criterio comparable para los ríos. Como es esperado, el río Nilo será el resultado porque su tamaño es el más largo.

Función maxOf()

La función maxOf() es similar a maxByOrNull(), pero esta en vez de retornar null cuando la colección es vacía, arroja una excepción del tipo NoSuchElementException. Por ello su sintaxis no posee retorno anulable como vemos en la sintaxis para mapas:

inline fun <K, V, R : Comparable<R>> Map<out K, V>.maxOf(
    selector: (Entry<K, V>) -> R
): R

Tomemos como ejemplo un mapa que almacena pares de parcelas de un terreno y su nivel de nitrógeno luego de aplicarle fertilizante. Se desea determinar cuál es el bloque con más concentración de nitrógeno, si no existe ningún dato entonces mostrar el mensaje de aviso:

fun main() {
    val blocksAndNitrogen = mutableMapOf<String, Double>()
    blocksAndNitrogen += "Bloque 1" to 15.843
    blocksAndNitrogen += "Bloque 2" to 15.474

    val maxNitrogenLevel = try {
        blocksAndNitrogen.maxOf { it.value }
    } catch (e: NoSuchElementException) {
        "Ingresa al menos un dato para realizar el cálculo"
    }
    println("Nivel máximo de Nitrógeno: $maxNitrogenLevel")
}

Salida:

15.843

En el código anterior usamos una expresión try..catch para asignar el valor a maxNitrogenLevel. Si blocksAndNitrogen no está vacío, se asignará el valor obtenido de maxOf(). De lo contrario procesamos la excepción en el catch y retornamos el String de aviso.

Si comentas las agregaciones de los dos elementos al mapa, obtendrás el siguiente resultado:

Ingresa al menos un dato para realizar el cálculo

Ú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!