La Función associateBy En Kotlin

En este tutorial estudiarás el uso de la función associateBy en Kotlin para crear un mapa a partir de una colección, asignando la clave que resulte de una función de selección.

Sintaxis De associateBy

La función associateBy hace parte de las operaciones de asociación, las cuales tienen como fin generar mapas a partir de colecciones de elementos.

Su propósito es crear un mapa con pares cuyas claves sean generadas por una función pasada como parámetro llamada keySelector. Los valores del par serán los elementos de la colección. Si dos o más comparten la misma clave, solo se añadirá el último de todos ellos.

Ejemplo de la función associateBy En Kotlin

En el ejemplo anterior la función selectora { x < 0} produce dos claves posibles: true para los elementos menores a cero y false para cero y los positivos.

Como ves, aunque -2 y -1 se asocian a la clave false, tan solo se añade -1 ya que es el último elemento leído. De forma similar sucede con la clave true, solo 1 es añadido a la entrada. En Kotlin podemos materializar la asociación así:

fun main() {
    val numbers = listOf(-2, -1, 0, 1)
    println(numbers.associateBy { it < 0 }) // {true=-1, false=1}
}

La siguiente es la sintaxis de la función de extensión associateBy{ } para diferentes tipos:

// Arreglos
inline fun <T, K> Array<out T>.associateBy(
    keySelector: (T) -> K
): Map<K, T>

// Iterables
inline fun <T, K> Iterable<T>.associateBy(
    keySelector: (T) -> K
): Map<K, T>

// Strings
inline fun <K> CharSequence.associateBy(
    keySelector: (Char) -> K
): Map<K, Char>

Y los siguiente son diferentes configuraciones de keySelector para crear diferentes claves de tipo K.

fun main() {
    val numbers = listOf(-2, -1, 0, 1)
    println(numbers.associateBy { 1 }) // {1=1}

    val temperatures = floatArrayOf(32.1f, 40.5f, 30f, 30f, 14f)
    println(temperatures.associateBy { it })
    // {32.1=32.1, 40.5=40.5, 30.0=30.0, 14.0=14.0}

    println("Develou.com".associateBy { it.toByte() })
    // {68=D, 101=e, 118=v, 108=l, 111=o, 117=u, 46=., 99=c, 109=m}
}

El código anterior muestra como el valor 30f de temperatures y los caracteres 'e' y 'o' de "Develou.com" fueron omitidos por associateBy para asegurar la inclusión del último valor adecuado.

Y al usar el literal constante 1 como función de selección de clave, los tres primeros elementos de numbers fueron omitidos y solo se añadió 1 para el par resultante.

En definitiva, si buscas crear un mapa cuyos valores sean únicos y no te afecte el descarte de elementos de la colección inicial, usar associateBy te será de gran ayuda.

Ejemplo De La Función associateBy

Considera el escenario de modelado para la clase de datos que representa las cartas de un videojuego.

data class Card(val name: String, val cost: Int)

Ahora supón que el jugador tiene en su mano varias cartas y debido a un efecto en el juego se desea conservar un ejemplar de cada costo y descartar el resto. Si hay costos repetidos se debe conservar la carta de más a la derecha.

fun main() {
    val hand = listOf(
        Card("Primer día de escuela", 0),
        Card("Humildad", 1),
        Card("Micromomia", 2),
        Card("Justicia de la luz", 1),
        Card("Protector argenta", 2)
    )

    hand.associateBy { it.cost }
        .forEach { (cost, card) -> println("$cost: $card") }
}

Salida:

0: Card(name=Primer día de escuela, cost=0)
1: Card(name=Justicia de la luz, cost=1)
2: Card(name=Protector argenta, cost=2)

El resultado de la asociación es el mapa sin las cartas "Humildad" y "Micromomia" como esperábamos.

Recuerda que la función lambda de antes puedes reescribirla con la referencia del miembro cost:

hand.associateBy(Card::cost)

Usar associateBy Con Transformación

La función associateBy tiene una segunda forma para recibir una función que transforme los valores que será incluidos en los pares del mapa final.

Por ejemplo, si en el ejemplo anterior no quisiéramos tener valores del tipo Card, si no que solo necesitáramos los Strings de sus nombres, entonces pasamos una función para expresar esta mapeado:

fun main() {    
    val hand = listOf(
        Card("Primer día de escuela", 0),
        Card("Humildad", 1),
        Card("Micromomia", 2),
        Card("Justicia de la luz", 1),
        Card("Protector argenta", 2)
    )



    // associateBy con transform
    println(hand.associateBy({ it.cost }, { it.name }))
}

Salida:

{0=Primer día de escuela, 1=Justicia de la luz, 2=Protector argenta}

Aplicando esta variante podrás incluir el mapeado en una sola operación sin tener que incluir otro proceso de formato. Obviamente también es posible usar las referencias de los miembros si tu transformación es simple:

println(hand.associateBy(Card::cost, Card::name))

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