39 - Expresiones lambda: Acceso a las variables externas a la expresión lambda

Hemos visto en conceptos anteriores que una expresión lambda es cuando enviamos a una función de orden superior directamente una función anónima.
Dentro de la función lambda podemos acceder a los parámetros de la misma si los tiene, definir variables locales y veremos ahora que podemos acceder a variables externas a la expresión lambda definida.

Problema 1

Confeccionar una función de orden superior que reciba un arreglo de enteros y una función con un parámetro de tipo Int y que no retorne nada.

La función debe enviar cada elemento del arreglo a la expresión lambda definida.

En la función main definir un arreglo de enteros de 10 elementos y almacenar valores aleatorios comprendidos entre 0 y 99.

Imprimir del arreglo:

  • La cantidad de múltiplos de 3
  • La suma de todas las componentes superiores a 50

Proyecto154 - Principal.kt

fun recorrerTodo(arreglo: IntArray, fn:(Int) -> Unit) {
    for(elemento in arreglo)
        (fn(elemento))
}

fun main(parametro: Array<String>) {
    val arreglo1 = IntArray(10)
    for (i in arreglo1.indices)
        arreglo1[i] = ((Math.random() * 100)).toInt()
    println("Impresion de todo el arreglo")
    for (elemento in arreglo1)
        print("$elemento ")
    println()
    var cantidad = 0
    recorrerTodo(arreglo1) {
        if (it % 3 == 0)
            cantidad++
    }
    println("La cantidad de elementos múltiplos de 3 son $cantidad")
    var suma = 0
    recorrerTodo(arreglo1) {
        if (it > 50)
            suma += it
    }
    println("La suma de todos los elementos mayores a 50 es $suma")
}

Lo más importante es entender y tener en cuenta que una variable que definamos fuera de la expresión lambda podemos accederla dentro de la expresión:

    var cantidad = 0
    recorrerTodo(arreglo1) {
        if (it % 3 == 0)
            cantidad++
    }
    println("La cantidad de elementos múltiplos de 3 son $cantidad")

La variable cantidad es de tipo Int y se inicializa en 0 previo a llamar a la funcióm recorrerTodo y pasar la expresión lambda donde incrementamos el contador "cantidad" cada vez que analizamos un elemento del arreglo que nos envía la función de orden superior.

Cuando finalizan todas las ejecuciones de la expresión lambda procedemos a mostrar el contador.

De forma similar para sumar todas las componentes del arreglo que tienen un valor superior a 50 llamamos a la función recorrerTodo y en la expresión lambda acumulamos la componente siempre y cuando tenga almacenado un valor superior a 50:

    var suma = 0
    recorrerTodo(arreglo1) {
        if (it > 50)
            suma += it
    }
    println("La suma de todos los elementos mayores a 50 es $suma")

Otra cosa que debe quedar claro que cuando una función no retorna dato y es un parámetro de otra función es obligatorio indicar el objeto Unit como dato de devolución:

fun recorrerTodo(arreglo: IntArray, fn:(Int) -> Unit) {

La función recorrerTodo también no retorna dato pero Kotlin interpreta por defecto que devuelve un tipo Unit. Luego generalmente no se utiliza la sintaxis:

fun recorrerTodo(arreglo: IntArray, fn:(Int) -> Unit): Unit {

No es común indicar el objeto Unit si la función no retorna dato, por defecto Kotlin sabe que debe retornar un objeto Unit.

La clase IntArray como las otras clases similares ByteArray, ShortArray, FloatArray etc. disponen de un método llamado forEach que se le debe enviar una expresión lambda, la cual se ejecuta para cada elemento del arreglo de forma similar a la función que creamos recorrerTodo.

Problema 2

Resolver el mismo problema anterior pero emplear el método forEach que dispone la clase IntArray para analizar todas las componentes del arreglo.

En la función main definir un arreglo de enteros de 10 elementos y almacenar valores aleatorios comprendidos entre 0 y 99.

Imprimir del arreglo:

  • La cantidad de múltiplos de 3
  • La suma de todas las componentes superiores a 50

Proyecto155 - Principal.kt

fun main(parametro: Array<String>) {
    val arreglo1 = IntArray(10)
    for (i in arreglo1.indices)
        arreglo1[i] = ((Math.random() * 100)).toInt()
    println("Impresion de todo el arreglo")
    for (elemento in arreglo1)
        print("$elemento ")
    println()
    var cantidad = 0
    arreglo1.forEach {
        if (it % 3 == 0)
            cantidad++
    }
    println("La cantidad de elementos múltiplos de 3 son $cantidad")
    var suma = 0
    arreglo1.forEach {
        if (it > 50)
            suma += it
    }
    println("La suma de todos los elementos mayores a 50 es $suma")
}

Es más lógico utilizar la función de orden superior forEach que dispone la clase IntArray en lugar de crear nuestra propia función (lo hicimos en el problema anterior con el objetivo de practicar la creación de funciones de orden superior)

Para contar la cantidad de múltiplos de 3 definimos un contador previo a llamar a forEach:

    var cantidad = 0
    arreglo1.forEach {
        if (it % 3 == 0)
            cantidad++
    }
    println("La cantidad de elementos múltiplos de 3 son $cantidad")

Recordar que dentro de la expresión lambda tenemos acceso a la variable externa cantidad.

Problema 3

Declarar una clase Persona con las propiedades nombre y edad, definir como métodos su impresión y otra que retorna true si es mayor de edad o false en caso contrario (18 años o más para ser mayor)
En la función main definir un arreglo con cuatro elementos de tipo Persona. Calcular cuantas personas son mayores de edad llamando al método forEach de la clase Array.

Proyecto156 - Principal.kt

class Persona(val nombre: String, val edad: Int) {
    fun imprimir() {
        println("Nombre: $nombre  Edad: $edad")
    }

    fun esMayor() = if (edad >= 18) true else false
}

fun main(parametro: Array<String>) {
    val personas: Array<Persona> = arrayOf(Persona("ana", 22),
            Persona("juan", 13),
            Persona("carlos", 6),
            Persona("maria", 72))
    println("Listado de personas")
    for(per in personas)
        per.imprimir()
    var cant = 0
    personas.forEach {
        if (it.esMayor())
            cant++
    }
    println("Cantidad de personas mayores de edad: $cant")
}

También la clase Array dispone de un método forEach que le pasamos una expresión lambda y recibe como parámetro un elemento del Array cada vez que se ejecuta. Mediante el parámetro it que en este caso es de la clase Persona analizamos si es mayor de edad:

    var cant = 0
    personas.forEach {
        if (it.esMayor())
            cant++
    }
    println("Cantidad de personas mayores de edad: $cant")

Siempre recordemos que utilizamos la palabra it para que sea más conciso nuestro programa, en la forma larga podemos escribir:

    var cant = 0
    personas.forEach {persona: Persona ->
        if (persona.esMayor())
            cant++
    }
    println("Cantidad de personas mayores de edad: $cant")

Problema propuesto

  • Declarar una clase Dado que tenga como propiedad su valor y dos métodos que permitan tirar el dado e imprimir su valor.
    En la main definir un Array con 5 objetos de tipo Dado.
    Tirar los 5 dados e imprimir cuantos 1, 2, 3, 4, 5 y 6 salieron.
Solución
Proyecto157

class Dado (var valor: Int = 1){

    fun tirar() {
        valor = ((Math.random() * 6) + 1).toInt()
    }

    fun imprimir() {
        println("Valor del dado: $valor")
    }
}

fun main(parametro: Array<String>) {
    var dados: Array<Dado> = arrayOf(Dado(), Dado(), Dado(), Dado(), Dado())
    for(dado in dados)
        dado.tirar()
    for(dado in dados)
        dado.imprimir()
    var cant1 = 0
    var cant2 = 0
    var cant3 = 0
    var cant4 = 0
    var cant5 = 0
    var cant6 = 0
    dados.forEach {
        when (it.valor) {
            1 -> cant1++
            2 -> cant2++
            3 -> cant3++
            4 -> cant4++
            5 -> cant5++
            6 -> cant6++
        }
    }
    println("Cantidad de dados que tienen el valor 1: $cant1")
    println("Cantidad de dados que tienen el valor 2: $cant2")
    println("Cantidad de dados que tienen el valor 3: $cant3")
    println("Cantidad de dados que tienen el valor 4: $cant4")
    println("Cantidad de dados que tienen el valor 5: $cant5")
    println("Cantidad de dados que tienen el valor 6: $cant6")
}