1. Introducción
En el codelab Cómo usar tipos de funciones y expresiones lambda en Kotlin, aprendiste sobre funciones de orden superior, que son funciones que toman otras funciones como parámetros o muestran una función, como repeat()
. Las funciones de orden superior son especialmente relevantes para las colecciones, ya que te ayudan a realizar tareas comunes, como ordenar o filtrar, con menos código. Ahora que tienes una base sólida que funciona con las colecciones, es hora de revisar las funciones de orden superior.
En este codelab, aprenderás sobre una variedad de funciones que se pueden usar en tipos de colección, incluidas las siguientes:forEach()
, map()
, filter()
, groupBy()
, fold()
y sortedBy()
. En el proceso, adquirirás práctica adicional para trabajar con expresiones lambda.
Requisitos previos
- Conocimientos de tipos de funciones y expresiones lambda
- Conocimientos de la sintaxis lambda final, como el caso de la función
repeat()
- Conocimientos de diversos tipos de colecciones en Kotlin, como
List
Qué aprenderás
- Cómo incorporar expresiones lambda en strings
- Cómo usar varias funciones de orden superior con la colección
List
, incluidasforEach()
,map()
,filter()
,groupBy()
,fold()
ysortedBy()
Requisitos
- Un navegador web con acceso a Playground de Kotlin
2. forEach() y plantillas de string con lambdas
Código de inicio
En los siguientes ejemplos, tomarás una List
que representará el menú de galletas de una panadería y usarás funciones de orden superior a fin de darle formato al menú de diferentes maneras.
Para comenzar, configura el código inicial.
- Ve al Playground de Kotlin.
- Arriba de la función
main()
, agrega la claseCookie
. Cada instancia deCookie
representa un elemento en el menú, que contiene unname
, unprice
y otra información sobre la galleta.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
fun main() {
}
- Debajo de la clase
Cookie
, fuera demain()
, crea una lista de galletas como se muestra. Se infiere que el tipo esList<Cookie>
.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
val cookies = listOf(
Cookie(
name = "Chocolate Chip",
softBaked = false,
hasFilling = false,
price = 1.69
),
Cookie(
name = "Banana Walnut",
softBaked = true,
hasFilling = false,
price = 1.49
),
Cookie(
name = "Vanilla Creme",
softBaked = false,
hasFilling = true,
price = 1.59
),
Cookie(
name = "Chocolate Peanut Butter",
softBaked = false,
hasFilling = true,
price = 1.49
),
Cookie(
name = "Snickerdoodle",
softBaked = true,
hasFilling = false,
price = 1.39
),
Cookie(
name = "Blueberry Tart",
softBaked = true,
hasFilling = true,
price = 1.79
),
Cookie(
name = "Sugar and Sprinkles",
softBaked = false,
hasFilling = false,
price = 1.39
)
)
fun main() {
}
Cómo aplicar un bucle a una lista con forEach()
La primera función de orden superior que aprenderás es forEach()
. forEach()
ejecuta la función pasada como parámetro una vez por cada elemento de la colección. Esto funciona de manera similar a la función repeat()
o a un bucle for
. La lambda se ejecuta para el primer elemento, luego para el segundo, y así sucesivamente, hasta que se haya ejecutado para cada elemento de la colección. La firma del método es la siguiente:
forEach(action: (T) -> Unit)
forEach()
toma un solo parámetro de acción: una función de tipo (T) -> Unit
.
T
corresponde a cualquier tipo de datos que contenga la colección. Debido a que la lambda toma un solo parámetro, puedes omitir el nombre y hacer referencia al parámetro con it
.
Usa la función forEach()
para imprimir los elementos de la lista cookies
.
- En
main()
, llama aforEach()
en la listacookies
mediante la sintaxis lambda final. Debido a que la lambda final es el único argumento, puedes omitir los paréntesis cuando llamas a la función.
fun main() {
cookies.forEach {
}
}
- En el cuerpo de la lambda, agrega una sentencia
println()
que imprimait
.
fun main() {
cookies.forEach {
println("Menu item: $it")
}
}
- Ejecuta tu código y observa el resultado. Todo lo que imprime es el nombre del tipo (
Cookie
) y un identificador único para el objeto, pero no el contenido del objeto.
Menu item: Cookie@5a10411 Menu item: Cookie@68de145 Menu item: Cookie@27fa135a Menu item: Cookie@46f7f36a Menu item: Cookie@421faab1 Menu item: Cookie@2b71fc7e Menu item: Cookie@5ce65a89
Cómo incorporar expresiones en cadenas
Cuando te presentamos por primera vez las plantillas de cadenas, viste cómo el símbolo de dólar ($
) se podía usar con un nombre de variable para insertarlo en una cadena. Sin embargo, esto no funciona como se espera cuando se combina con el operador de punto (.
) para acceder a propiedades.
- En la llamada a
forEach()
, modifica el cuerpo de la expresión lambda para insertar$it.name
en la string.
cookies.forEach {
println("Menu item: $it.name")
}
- Ejecuta tu código. Observa que, con esta acción, se inserta el nombre de la clase,
Cookie
, y un identificador único del objeto, seguido de.name
. No se accede al valor de la propiedadname
.
Menu item: Cookie@5a10411.name Menu item: Cookie@68de145.name Menu item: Cookie@27fa135a.name Menu item: Cookie@46f7f36a.name Menu item: Cookie@421faab1.name Menu item: Cookie@2b71fc7e.name Menu item: Cookie@5ce65a89.name
Para acceder a las propiedades y, luego, incorporarlas en una cadena, necesitas una expresión. Puedes hacer que una expresión sea parte de una plantilla de cadena si la encierras entre llaves.
La expresión lambda se coloca entre las llaves de apertura y cierre. Puedes acceder a propiedades, realizar operaciones matemáticas, llamar a funciones, etc., y el valor que se muestra de la expresión lambda se inserta en la string.
Modifiquemos el código de modo que el nombre se inserte en la string.
- Encierra
it.name
entre llaves para convertirla en una expresión lambda.
cookies.forEach {
println("Menu item: ${it.name}")
}
- Ejecuta tu código. El resultado contiene el
name
de cadaCookie
.
Menu item: Chocolate Chip Menu item: Banana Walnut Menu item: Vanilla Creme Menu item: Chocolate Peanut Butter Menu item: Snickerdoodle Menu item: Blueberry Tart Menu item: Sugar and Sprinkles
3. map()
La función map()
te permite transformar una colección en otra nueva con la misma cantidad de elementos. Por ejemplo: map()
podría transformar una List<Cookie>
en una List<String>
que solo contenga el name
de la galleta, siempre que le indiques a la función map()
cómo crear una String
de cada elemento Cookie
.
Supongamos que escribes una app que muestra el menú interactivo de una panadería. Cuando el usuario navegue a la pantalla en la que se muestra el menú de galletas, es posible que desee ver los datos presentados de forma lógica, como el nombre seguido del precio. Puedes crear una lista de strings con formato de modo que incluya los datos relevantes (nombre y precio) mediante la función map()
.
- Quita todo el código anterior de
main()
. Crea una variable nueva llamadafullMenu
y establécela en el resultado de llamar amap()
en la listacookies
.
val fullMenu = cookies.map {
}
- En el cuerpo de la lambda, agrega una string con formato de modo que incluya el
name
y elprice
deit
.
val fullMenu = cookies.map {
"${it.name} - $${it.price}"
}
- Imprime el contenido de
fullMenu
. Puedes hacerlo medianteforEach()
. La colecciónfullMenu
que se muestra a partir demap()
tiene el tipoList<String>
en lugar deList<Cookie>
. CadaCookie
encookies
corresponde a unaString
enfullMenu
.
println("Full menu:")
fullMenu.forEach {
println(it)
}
- Ejecuta tu código. El resultado coincide con el contenido de la lista
fullMenu
.
Full menu: Chocolate Chip - $1.69 Banana Walnut - $1.49 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Sugar and Sprinkles - $1.39
4. filter()
La función filter()
te permite crear un subconjunto de una colección. Por ejemplo, si tuvieras una lista de números, podrías usar filter()
para crear una lista nueva que solo contenga números divisibles por 2.
Mientras que el resultado de la función map()
siempre genera una colección del mismo tamaño, filter()
proporciona una colección del mismo tamaño o uno más pequeño que el de la colección original. A diferencia de map()
, la colección resultante también tiene el mismo tipo de datos, por lo que filtrar una List<Cookie>
generará otra List<Cookie>
.
Al igual que las funciones map()
y forEach()
, filter()
toma una sola expresión lambda como parámetro. La lambda tiene un solo parámetro que representa cada elemento de la colección y muestra un valor de tipo Boolean
.
Para cada elemento de la colección, se cumple lo siguiente:
- Si el resultado de la expresión lambda es
true
, el elemento se incluirá en la colección nueva. - Si el resultado es
false
, el elemento no se incluirá en la colección nueva.
Esto resulta útil si deseas obtener un subconjunto de datos en la app. Por ejemplo, supongamos que la panadería quiere destacar sus galletas blandas en una sección separada del menú. Antes de imprimir los elementos, puedes aplicar la función filter()
a la lista de cookies
.
- En
main()
, crea una nueva variable llamadasoftBakedMenu
y establécela en el resultado de llamar afilter()
en la lista decookies
.
val softBakedMenu = cookies.filter {
}
- En el cuerpo de la lambda, agrega una expresión booleana de modo que verifique si la propiedad
softBaked
de la galleta resultatrue
. ComosoftBaked
es unBoolean
en sí, el cuerpo de la lambda solo debe contenerit.softBaked
.
val softBakedMenu = cookies.filter {
it.softBaked
}
- Imprime el contenido de
softBakedMenu
conforEach()
.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Ejecuta tu código. El menú se imprime como antes, pero solo incluye las galletas blandas.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79
5. groupBy()
La función groupBy()
se puede usar para convertir una lista en un mapa con base en una función. Cada valor único que se muestra de la función se convierte en una clave en el mapa resultante. Los valores de cada clave son todos los elementos de la colección que produjeron ese valor único.
El tipo de datos de las claves coincide con el que se muestra de la función que se pasó a groupBy()
. El tipo de datos de los valores es una lista de elementos de la lista original.
Esto puede resultar difícil de conceptualizar. Comencemos con un ejemplo simple. Dada la misma lista de números que antes, agrúpalos en impares o pares.
Puedes verificar si un número es impar o par dividiéndolo por 2
y comprobando si el resto es 0
o 1
. Si el resto es 0
, el número es par. De lo contrario, si el resto es 1
, el número es impar.
Esto se puede lograr con el operador de módulo (%
), que divide el dividendo en el lado izquierdo de una expresión por el divisor del lado derecho.
En lugar de mostrar el resultado de la división, como hace el operador de división (/
), el operador de módulo muestra el resto. Esto lo hace útil para verificar si un número es par o impar.
Se llama a la función groupBy()
con la siguiente expresión lambda: { it % 2 }
.
El mapa resultante tiene dos claves: 0
y 1
. Cada clave tiene un valor de tipo List<Int>
. La lista para la clave 0
contiene todos los números pares, y aquella para la clave 1
contiene todos los impares.
Un caso de uso del mundo real podría ser una app de fotos que agrupa fotos según el tema o la ubicación donde se tomaron. Para nuestro menú de panadería, agrupemos el menú en función de si una galleta es blanda o no.
Usa la función groupBy()
a fin de agrupar el menú según la propiedad softBaked
.
- Quita la llamada a
filter()
del paso anterior.
Código para quitar
val softBakedMenu = cookies.filter {
it.softBaked
}
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Llama a la función
groupBy()
en la lista decookies
y almacena el resultado en una variable llamadagroupedMenu
.
val groupedMenu = cookies.groupBy {}
- Pasa una expresión lambda que muestre
it.softBaked
. El tipo de datos que se mostrará seráMap<Boolean, List<Cookie>>
.
val groupedMenu = cookies.groupBy { it.softBaked }
- Crea una variable
softBakedMenu
que contenga el valor degroupedMenu[true]
y una variablecrunchyMenu
que contenga el valor degroupedMenu[false]
. Como el resultado de suscribir un elementoMap
puede ser nulo, puedes usar el operador Elvis (?:
) para mostrar una lista vacía.
val softBakedMenu = groupedMenu[true] ?: listOf()
val crunchyMenu = groupedMenu[false] ?: listOf()
- Agrega código con el fin de imprimir el menú de las galletas blandas, seguido del menú de las galletas crujientes.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
println("Crunchy cookies:")
crunchyMenu.forEach {
println("${it.name} - $${it.price}")
}
- Ejecuta tu código. Con la función
groupBy()
, dividirás la lista en dos, según el valor de una de las propiedades.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Crunchy cookies: Chocolate Chip - $1.69 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Sugar and Sprinkles - $1.39
6. fold()
La función fold()
se usa para generar un valor único a partir de una colección. Por lo general, se usa para calcular un total de precios o sumar todos los elementos de una lista para encontrar un promedio.
La función fold()
toma dos parámetros:
- Un valor inicial; el tipo de datos se infiere cuando se llama a la función (es decir, se deduce que un valor inicial de
0
es unInt
) - Una expresión lambda que muestra un valor con el mismo tipo que el valor inicial
La expresión lambda tiene dos parámetros adicionales:
- El primero se conoce como acumulador. Tiene el mismo tipo de datos que el valor inicial. Piensa en esto como un total acumulado. Cada vez que se llama a la expresión lambda, el acumulador equivaldrá al valor que se mostró cuando se llamó a la expresión lambda por última vez.
- El segundo es el mismo tipo que cada elemento de la colección.
Al igual que con otras funciones que viste, se llama a la expresión lambda para cada elemento de una colección, de modo que puedes usar fold()
como una forma concisa de sumar todos los elementos.
Usemos fold()
para calcular el precio total de todas las galletas.
- En
main()
, crea una nueva variable llamadatotalPrice
y establécela en el resultado de llamar afold()
en la lista decookies
. Pasa0.0
como valor inicial. Se infiere que el tipo esDouble
.
val totalPrice = cookies.fold(0.0) {
}
- Deberás especificar ambos parámetros para la expresión lambda. Usa
total
para el acumulador ycookie
para el elemento de la colección. Usa la flecha (->
) después de la lista de parámetros.
val totalPrice = cookies.fold(0.0) {total, cookie ->
}
- En el cuerpo de la lambda, calcula la suma de
total
ycookie.price
. Se infiere que es el valor que se muestra y se pasará como eltotal
la próxima vez que se llame a la lambda.
val totalPrice = cookies.fold(0.0) {total, cookie ->
total + cookie.price
}
- Imprime el valor de
totalPrice
, con el formato de una string para facilitar la lectura.
println("Total price: $${totalPrice}")
- Ejecuta tu código. El resultado debe ser igual a la suma de los precios de la lista de
cookies
.
... Total price: $10.83
7. sortedBy()
Cuando viste por primera vez las colecciones, aprendiste que la función sort()
se podía usar para ordenar los elementos. Sin embargo, esto no funcionará en una colección de objetos Cookie
. La clase Cookie
tiene varias propiedades, y Kotlin no sabrá cuáles (name
, price
, etc.) quieres usar para ordenar.
En estos casos, las colecciones de Kotlin proporcionan una función sortedBy()
. sortedBy()
te permite especificar una expresión lambda que muestra la propiedad según la cual deseas ordenar. Por ejemplo, si deseas ordenar por price
, la lambda mostrará it.price
. Siempre y cuando el tipo de datos del valor tenga un orden natural (las cadenas se ordenan alfabéticamente y los valores numéricos se ordenan de forma ascendente), se ordenará como si se tratara de colecciones de ese tipo.
Usarás sortedBy()
para ordenar la lista de galletas alfabéticamente.
- En
main()
, después del código existente, agrega una variable nueva llamadaalphabeticalMenu
y configúrala para llamar asortedBy()
en la lista decookies
.
val alphabeticalMenu = cookies.sortedBy {
}
- En la expresión lambda, muestra
it.name
. La lista resultante seguirá siendo de tipoList<Cookie>
, pero se ordenará según elname
.
val alphabeticalMenu = cookies.sortedBy {
it.name
}
- Imprime los nombres de las galletas en
alphabeticalMenu
. Puedes usar la funciónforEach()
para imprimir cada nombre en una línea nueva.
println("Alphabetical menu:")
alphabeticalMenu.forEach {
println(it.name)
}
- Ejecuta tu código. Los nombres de las galletas están impresos en orden alfabético.
... Alphabetical menu: Banana Walnut Blueberry Tart Chocolate Chip Chocolate Peanut Butter Snickerdoodle Sugar and Sprinkles Vanilla Creme
8. Conclusión
¡Felicitaciones! Acabas de ver varios ejemplos de cómo se pueden usar las funciones de orden superior con las colecciones. Las operaciones comunes, como ordenar y filtrar, se pueden realizar en una sola línea de código, lo que hace que tus programas sean más concisos y expresivos.
Resumen
- Puedes aplicar un bucle a cada elemento de una colección con
forEach()
. - Las expresiones se pueden insertar en cadenas.
map()
se usa para darle formato a los elementos de una colección, a menudo como una colección de otro tipo de datos.filter()
puede generar un subconjunto de una colección.groupBy()
divide una colección según un valor que se muestra de una función.fold()
convierte una colección en un solo valor.sortedBy()
se usa para ordenar una colección según una propiedad especificada.