En este tutorial verás cómo usar a la función groupingBy
en Kotlin para agrupar los elementos de una colección y luego aplicarles una operación a todos los grupos generados, generando así un mapa con los resultados por cada grupo.
Función groupingBy()
La función de extensión groupingBy()
funciona similar a groupBy()
. Ambas toman una función lambda que hace de selector de clave y así crear grupos de elementos con dichas claves.
La diferencia está en que groupingBy()
retorna en una instancia del tipo Grouping
. Esta interfaz te permite aplicar funciones de agregación sobre los grupos, las cuales retornan un mapa con los valores finales de cada grupo:
// groupingBy() en arreglos
inline fun <T, K> Array<out T>.groupingBy(
crossinline keySelector: (T) -> K
): Grouping<T, K>
// groupingBy() en iterables
inline fun <T, K> Iterable<T>.groupingBy(
crossinline keySelector: (T) -> K
): Grouping<T, K>
Las funciones que puedes usar sobre el resultado de groupingBy()
son:
eachCount()
: Cuentas los elementos por cada grupo. El resultado en un mapa con pares cuyo valor es la cantidad contadafold()
: Aplica una operación acumulativa a partir de los valores de cada grupo. El valor inicial del acumulado lo pasas como primer parámetroreduce()
: Aplicar una operación que reduce a una instancia como resultado de cada grupoaggregate()
: Esta función es la base para generar las otras funciones nombradas. Toma una operación y la aplica sobre cada elemento para obtener el resultado acumulado
Contar El Número De Empleados En Cada Departamento
Tomemos como ejemplo un sencillo diseño para modelar a los empleados de una compañía que pertenecen a un departamento.
data class Employee(val name: String, val salary: Int, val department: String) {
override fun toString(): String {
return "%-10s %4s %s".format(name, salary, department)
}
}
val employees = listOf(
Employee("Carolina", 2000, "Finanzas"),
Employee("Shirley", 1500, "Recursos humanos"),
Employee("Carlos", 1250, "Finanzas"),
Employee("Jill", 4200, "Marketing"),
Employee("Pedro", 500, "Logística"),
Employee("Ana", 550, "Logística"),
Employee("Santiago", 1200, "Logística"),
)
fun main() {
employees.forEach(::println)
}
Salida:
Carolina 2000 Finanzas
Shirley 1500 Recursos humanos
Carlos 1250 Finanzas
Jill 4200 Marketing
Pedro 500 Logística
Ana 550 Logística
Santiago 1200 Logística
La clase Employee
se constituye del nombre, salario y departamento a que pertenece el empleado. Y como ves la lista employees
contiene siete empleados que usaremos para probar las diferentes funciones asociadas a Grouping
.
El primer ejemplo será contar la cantidad de empleados que existen por departamento actualmente. Esto significa que debemos agrupar a la lista por la propiedad department
y luego aplicar eachCount()
:
fun <T, K> Grouping<T, K>.eachCount(): Map<K, Int>
La función eachCount()
retorna un mapa con pares cuyos valores son la cantidad contada por cada clave.
fun main() {
val employeesByDepartment = employees.groupingBy(Employee::department).eachCount()
println("Cantidad de empleados por departamento:")
employeesByDepartment.forEach { (department, count) ->
println("$department tiene $count empleados")
}
}
Salida:
Cantidad de empleados por departamento:
Finanzas tiene 2 empleados
Recursos humanos tiene 1 empleados
Marketing tiene 1 empleados
Logística tiene 3 empleados
Calcular La Suma De Salarios Por Departamento
Para computar la suma total en cada departamento recurrimos de nuevo a la agrupación de la lista employees
por la propiedad department
.
La operación de suma es posible conseguirla a través del método fold()
o agreggate()
. Nuestro objetivo es acumular la suma de la propiedad salary
en cada grupo existente.
inline fun <T, K, R> Grouping<T, K>.fold(
initialValue: R,
operation: (accumulator: R, element: T) -> R
): Map<K, R>
Veamos la solución:
fun main() {
val salariesSumByDepartment = employees
.groupingBy(Employee::department)
.fold(0) { sum, employee -> sum + employee.salary }
println("Suma total de salarios por departamento:")
salariesSumByDepartment.forEach { (department, salarySum) ->
println("$department: $$salarySum ")
}
}
Salida:
Suma total de salarios por departamento:
Finanzas: $3250
Recursos humanos: $1500
Marketing: $4200
Logística: $2250
Obtener El Salario Más Alto Por Departamento
En este caso la operación de acumulación que necesitamos no es aritmética, si no de reducción. Necesitamos imprimir un solo valor seleccionado por cada departamento luego de usar groupingBy()
.
La solución consta del uso de la función reduce()
, la cual nos fuerza a elegir una instancia de Employee
en la función lambda de operación.
inline fun <S, T : S, K> Grouping<T, K>.reduce(
operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S>
Como ves en la sintaxis, el acumulado comparte al tipo S
en común con element
. El cuerpo de la operación asigna al acumulado el empleado que se considere preponderante en cuanto a salario:
fun main() {
val salariesSumByDepartment = employees
.groupingBy(Employee::department)
.reduce { _, highestSalaryEmployee, employee ->
if (highestSalaryEmployee.salary > employee.salary) highestSalaryEmployee else employee
}
salariesSumByDepartment.forEach { (department, employee) ->
println("${employee.salary} es el mayor salario en $department")
}
}
Salida:
2000 es el mayor salario en Finanzas
1500 es el mayor salario en Recursos humanos
4200 es el mayor salario en Marketing
1200 es el mayor salario en Logística
La Función aggregate()
La función aggregate()
recibe una función de operación para acumular valores en un mapa con respecto a los grupos que se generan de Grouping
.
inline fun <T, K, R> Grouping<T, K>.aggregate(
operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R
): Map<K, R>
Normalmente se usa cuando fold()
y reduce()
no cumplen con nuestras necesidades.
Por ejemplo, si quisieras obtener el valor de la suma de salarios por departamentos con aggregate()
, podrías usar el parámetro first
para inicializar el valor del acumulado y luego ir sumando los valores subsecuentes:
val salariesSumByDepartment = employees
.groupingBy(Employee::department)
.aggregate { _, sum: Int?, employee, first ->
if (first) {
employee.salary
} else {
sum!! + employee.salary
}
}
Ú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!