En este tutorial verás la sintaxis y uso de la función flatMap
en Kotlin con el fin de mapear y aplanar una colección anidada en una sola operación.
Sintaxis De La Función flatMap( )
Al igual que flatten()
, la función flatMap()
hace parte del grupo de operaciones de transformación para colecciones anidadas.
Su objetivo es permitirte transformar los elementos de una colección a través de una función lambda para luego aplicar el mismo efecto de flatten()
a las colecciones resultantes.
Tomemos como ilustración una lista de pares enteros, cuyos elementos deseamos juntar en una lista final. Al aplicar flatMap
el resultado de la transformación se vería así:
La función de transformación que pases será aplicada con map()
, en este caso sería convertir el par en una lista. Luego se aplica flatten()
para combinar los mapeados en la lista final [1, 1, 2, 2, 3, 3]
.
El anterior ejemplo puede ser escrito en Kotlin de la siguiente forma:
fun main() {
val pairs = listOf(1 to 1, 2 to 2, 3 to 3)
println(pairs.flatMap { it.toList() }) // [1, 1, 2, 2, 3, 3]
}
Como ves, flatMap()
soluciona la falta de flexibilidad de flatten()
al permitirte crear la colección anidada con la función de transformación. Esto se evidencia en la sintaxis de definición en la librería:
// Arreglos
inline fun <T, R> Array<out T>.flatMap(
transform: (T) -> Iterable<R>
): List<R>
// Iterables
inline fun <T, R> Iterable<T>.flatMap(
transform: (T) -> Iterable<R>
): List<R>
// Mapas
inline fun <K, V, R> Map<out K, V>.flatMap(
transform: (Entry<K, V>) -> Iterable<R>
): List<R>
El parámetro tipo función transform
toma como entrada a cada elemento de la colección y tiene como cuerpo un valor final de tipo Iterable<R> o Secuence<R>
.
Ejemplo De flatMap() En Arreglo
Considera un arreglo de strings que ha almacenado los caracteres validos propuestos por el usuario en la escritura de un programa hipotético. Se requiere combinarlos a todos en forma atómica e imprimirlos en la consola.
fun main() {
val allowedCharacterGroups = arrayOf("xyz", "123", ";:.")
println(allowedCharacterGroups.flatMap { it.asIterable() })
}
Salida:
[x, y, z, 1, 2, 3, ;, :, .]
En este ejemplo asIterable()
crea una instancia Iterable
para de cada String del arreglo allowedCharactersGroup
. Con esta transformación ya será posible tener los caracteres en la lista final.
Ejemplo De flatMap() En Lista
Supongamos que tenemos una lista de números impares y deseamos crear una lista que incluya cada número y sus resultados al ser multiplicados por dos y tres respectivamente.
fun main() {
val oddNumbers = listOf(1, 3, 5)
println(oddNumbers.flatMap { listOf(it, it * 2, it * 3) })
}
Salida:
[1, 2, 3, 3, 6, 9, 5, 10, 15]
En este ejemplo la transformación es la creación de una lista inicializada con la referencia it
. La primer posición es el número impar y las subsecuentes los productos por los literales enteros 2
y 3
.
Ejemplo De flatMap() Con Mapa
Ahora tomemos como referencia un mapa con pares que especifican una vocal como clave y la cantidad de veces que se repetirá en su valor. La idea es crear una lista final con n elementos repetidos.
fun main() {
val vowelsAndRepeatTimes = mapOf('a' to 1, 'e' to 4, 'i' to 2)
println(vowelsAndRepeatTimes.flatMap { (k, v) -> MutableList(v) { k } })
}
Salida:
[a, e, e, e, e, i, i]
La transformación para flatMap
anterior es el uso del constructor de MutableList
junto al tamaño inicial v y el valor de inicialización k.
Recordemos que por desestructuración de la declaración de parámetros de la función lambda, k
es la propiedad Map.Entry.key
y v
corresponde a Map.Entry.value
.
De esta forma el mapeado y aplanado arroja una lista con una a, cuatro es y dos íes.
Ejemplo De flatMap() Con Dueños Y Mascotas
En este ejemplo usemos un diseño simple orientado a objetos para ver las ventajas de flatMap()
en situaciones donde una clase se compone de otra.
Se tienen dos clases que modelan de forma plana la relación de una mascota a un dueño. En este caso una mascota puede ser de varios dueños al tiempo como muestra la propiedad owners
.
data class Owner(val name: String)
data class Pet(val name: String, val owners: List<Owner>)
Nuestro propósito es imprimir todos los nombres dueños que existen evitando duplicados. Veamos como flatMap()
puede ayudarnos:
fun main() {
val pets = listOf(
Pet("Coloso", listOf(Owner("Rupertina"), Owner("Jairo"))),
Pet("Ñiña", listOf(Owner("Oscar"), Owner("Ramiro"))),
Pet("Morocha", listOf(Owner("Rupertina"), Owner("Susana"))),
Pet("Astro", listOf(Owner("Rubén"), Owner("Mauren")))
)
println(pets
.flatMap { it.owners.map(Owner::name) }
.distinct()
)
}
Salida:
[Rupertina, Jairo, Oscar, Ramiro, Susana, Rubén, Mauren]
La solución del requerimiento anterior usó flatMap()
sobre la lista resultante del map()
de los nombres de los propietarios. Al final se usa distinct()
para obtener los nombres únicos.
Por esta razón el nombre de Rupertina aparece una sola vez, aunque sea la dueña de Coloso y Morocha.