Operaciones con colecciones VIII: obtener valores de agrupación

Operaciones con colecciones VIII: obtener valores de agrupación

Continuando la serie sobre operaciones con colecciones, en esta entrada vamos a ver un conjunto de funciones que realizan operaciones (de agrupación o agregación, del inglés aggregate operations) para calcular ciertos valores o índices de agrupación a partir de los datos de la colección, como por ejemplo, los valores mínimo y máximo, el número de elementos, la suma de todos los elementos, la media o la suma acumulada. Un ejemplo de operación de agrupamiento es el cálculo del promedio de temperatura a partir de los valores de temperatura diaria durante un mes.

Ejemplos de este tipo de funciones son:
  • min() y max(), que devuelven el elemento más pequeño y el más grande respectivamente.
  • average(), que devuelve el valor promedio en una colección de números.
  • sum(), que devuelve la suma de elementos en una colección de números.
  • count(), que devuelve el número de elementos en una colección.
fun main() {
    val gradosC = listOf(25.5, 26.1, 24.3, 25.2)

    println("Datos: ${gradosC.count()}")
    println("Máximo: %.1f".format(gradosC.max()))
    println("Mínimo: %.1f".format(gradosC.min()))
    println("Media: %.1f".format(gradosC.average()))
    println("Suma: %.1f".format(gradosC.sum()))
}

También hay otras funciones que sirven para recuperar los elementos más pequeños y más grandes mediante funciones de selección o mediante un comparador personalizado, como son:
  • maxBy() y minBy(), que mediante una función de selección devuelven el elemento con el valor más grande o más pequeño (o en caso de igualdad, el primero que encuentra).
  • maxWith() y minWith(), que tomando como referencia un objeto de tipo Comparator devuelven el elemento más grande o más pequeño (o en caso de igualdad, el primero que encuentra).
fun main() {
    val numeros = listOf(1, 2, 3, 4, 5)
    val moduloDos = numeros.minBy { it % 2 }
    println(moduloDos) // 2

    val strings = listOf("uno", "dos", "tres", "cuatro", "cinco")
    val masLargo = strings.maxWith(compareBy { it.length })
    println(masLargo) // cuatro
}

Además, hay funciones de suma avanzada o compleja que toman una función que opera sobre los elementos y devuelven la suma total de los valores de retorno:
  • sumBy() se aplica a funciones que devuelven valores de tipo Int en los elementos de la colección.
  • sumByDouble() funciona con funciones que devuelven valores de tipo Double.
val numeros = listOf(1, 2, 3, 4)
println(numeros.sumBy { it * 2 }) // 20
println(numeros.sumByDouble { it.toDouble() / 2 }) // 5.0

Por otro lado, existen las funciones reduce() y fold() que aplican la operación proporcionada a los elementos de la colección de forma secuencial y devuelven el resultado acumulado. La operación toma dos argumentos: el valor acumulado previamente y el elemento de colección, con la diferencia de que fold() toma un valor inicial y lo usa como el valor acumulado en el primer paso, mientras que reduce() usa en el primer paso el primer y el segundo elementos como argumentos de la operación. Más claro en un ejemplo comentado:
fun main() {
    val numeros = listOf(5, 2, 10, 4)

    val acumulado = numeros.reduce { sum, element -> sum + element }
    println(acumulado) // 5 -> (5 + 2) -> (7 + 10) -> (17 + 4) = 21

    val desdeDiez = numeros.fold(10) { sum, element -> sum + element }
    println(desdeDiez) // 10 + 5 -> (15 + 2) -> (17 + 10) -> (27 + 4) = 31

    val acumuladoDoble = numeros.fold(0) { sum, element -> sum + element * 2 }
    println(acumuladoDoble) // 5x2 -> (10 + 2x2) -> (14 + 10x2) -> (34 + 4x2) = 42

    // con reduce el primer elemento no se multiplica por 2:
    val acumuladoDobleReduce = numeros.reduce { sum, element -> sum + element * 2 }
    println(acumuladoDobleReduce) // 5 -> (5 + 2x2) -> (9 + 10x2) -> (29 + 4x2) = 37
}

Para aplicar estas funciones en el orden inverso, usamos las funciones reduceRight() y foldRight(), que operan de manera similar a reduce() y fold() pero comienzan desde el último elemento y avanzan hacia el anterior hasta el primero (además hay que tener en cuenta que en estas funciones los argumentos de la operación cambian su orden: primero va el elemento y luego el valor acumulado).

También podemos realizar operaciones que toman los índices de los elementos como parámetros con las funciones reduceIndexed() y foldIndexed() pasando el índice del elemento como primer argumento de la operación (y las funciones inversas que aplican las operaciones a los elementos de la colección de derecha a izquierda: reduceRightIndexed() y foldRightIndexed()).
fun main() {
    val numeros = listOf(5, 2, 10, 4)
    val sumaPosicionesPares = numeros.foldIndexed(0) { idx, suma, elemento -> if (idx % 2 == 0) suma + elemento else suma }
    println(sumaPosicionesPares) // 5 -> 5 -> (5 + 10) -> 15 = 15
    // idx = 0 -> 5 (suma + elemento)
    // idx = 1 -> 5 (suma)
    // idx = 2 -> 5 + 10 (suma + elemento)
    // idx = 3 -> 15 (suma)
}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos