En este tutorial verás el uso de la función zip
en Kotlin con el propósito de organizar dos listas en pares de elementos que correspondan en sus índices. Adicionalmente veremos su versión sobrecargada con una función de transformación y el uso de las funciones zipWithNext()
y unzip()
.
Función zip()
La función de extensión zip()
retorna una lista de pares creados a partir de dos colecciones. Estos son conformados por los elementos que tengan la misma posición en cada lista. Si los tamaños de las colecciones son diferentes, entonces la lista resultante tendrá el tamaño de la más pequeña.
// Sintaxis de zip() entre arreglos
infix fun <T, R> Array<out T>.zip(
other: Array<out R>
): List<Pair<T, R>>
// Sintaxis de zip() entre colecciones
infix fun <T, R> Iterable<T>.zip(
other: Iterable<R>
): List<Pair<T, R>>
// zip() entre arreglos y colecciones
infix fun <T, R> Array<out T>.zip(
other: Iterable<R>
): List<Pair<T, R>>
infix fun <T, R> Iterable<T>.zip(
other: Array<out R>
): List<Pair<T, R>>
La siguiente ilustración muestra cómo funciona la transformación para comprimir los elementos de las listas numbers = [1, 2, 3, 4, 5]
y vowels = [a, e, i, o, u]
:
El resultado es una lista con pares generados por las coincidencias de posiciones. La representación de este ejemplo ene Kotlin lo puedes escribir de la siguiente forma:
fun main() {
// zip()
val numbers = listOf(1, 2, 3, 4, 5)
val vowels = listOf('a', 'e', 'i', 'o', 'u')
println(numbers zip vowels)
}
Salida:
[(1, a), (2, e), (3, i), (4, o), (5, u)]
Es posible escribir numbers zip vowels
ya que zip()
es una función infix.
Función zip() Con Transformación
Existe una variante sobrecargada de zip()
para tomar una función lambda como parámetro para representar la el resultado final de la compresión. Por ejemplo, para arreglos la sintaxis es la siguiente:
inline fun <T, R, V> Array<out T>.zip(
other: Array<out R>,
transform: (a: T, b: R) -> V
): List<V>
El parámetro transform
toma como lista de argumentos a los elementos de a
y b
con el objetivo de usarlos en el resultado final de tipo V
.
Por ejemplo, se tienen un arreglo de cuatro números enteros y otro con cuatro números decimales. Multiplicar sus elementos y crear una lista a partir de los resultados finales:
fun main() {
// zip(transform)
val x = arrayOf(1, 2, 3, 4)
val y = arrayOf(0.2f, 0.7f, 0.9f)
val z = x.zip(y) { a, b -> a * b }
println(z)
}
Salida:
[0.2, 1.4, 2.6999998]
Función unzip()
Es posible conseguir el resultado inverso a zip()
a través de unzip()
. A partir de una colección de pares, se obtiene un par con las dos listas que representan la descompresión.
// unzip() en arreglos
fun <T, R> Array<out Pair<T, R>>.unzip(): Pair<List<T>, List<R>>
// unzip() en iterables
fun <T, R> Iterable<Pair<T, R>>.unzip(): Pair<List<T>, List<R>>
Supongamos que hemos recolectado las puntuaciones de varios jugadores en nuestro programa en una lista de pares. Si quisiéramos separar los pares en una lista con solo los nombres y otra con puntuaciones, entonces usamos unzip()
así:
fun main() {
// unzip()
val playersAndPoints = listOf(
"Isabela" to 5,
"Marcos" to 4,
"Katrina" to 10,
"Alfredo" to 7
)
val (players, points) = playersAndPoints.unzip()
println(
"""
|Lista de jugadores = $players
|Lista de puntos = $points
""".trimMargin()
)
}
Salida:
Lista de jugadores = [Isabela, Marcos, Katrina, Alfredo]
Lista de puntos = [5, 4, 10, 7]
Como ves, unzip()
dirige los elementos first
de cada par en playersAndPoints
hacia players
y los elementos second
hacia la lista points
. Luego imprimimos un String de múltiples líneas los contenidos de ambas colecciones.
Función zipWithNext()
La función de extensión zipWithNext()
retorna en una lista de pares con los elementos adyacentes de la colección invocadora.
fun <T> Iterable<T>.zipWithNext(): List<Pair<T, T>>
La siguiente ilustración muestra cómo funciona la creación de pares entre elementos vecinos en la lista de vocales:
Como ves, tanto el primero como el último elemento solo aparecen una vez. Los demás se incluyen en dos veces debido a la expresión de contigüidad. Este ejemplo se vería así en Kotlin:
fun main() {
// zipWithNext()
val listToZipWithNext= listOf('a', 'e', 'i', 'o', 'u')
println(listToZipWithNext.zipWithNext())
}
Salida:
[(a, e), (e, i), (i, o), (o, u)]
Función zipWithNext() Con Transformación
Al igual que zip()
, existe una versión de zipWithNext()
que recibe una función de transformación. El parámetro transform
toma a los elementos vecinos para crear sentencias que terminen en una lista con los resultados finales.
inline fun <T, R> Iterable<T>.zipWithNext(
transform: (a: T, b: T) -> R
): List<R>
Tomemos como ejemplo una lista con nombres de ciudades y la impresión en pantalla al realizar un recorrido turístico:
fun main() {
// zipWithNext(transform)
val cities = listOf("Cali","Bogotá","Armenia","Barranquilla")
println(cities.zipWithNext { a, b -> "De $a a $b" })
}
Salida:
[De Cali a Bogotá, De Bogotá a Armenia, De Armenia a Barranquilla]
Pasamos una función lambda que genera un String
producto de la concatenación de los nombres de las ciudades. Esto nos permite obtener una lista de rutas entre punto y punto.