Maximiliano Burgos
Posted on December 13, 2022
Bienvenido/a a otro capítulo del Curso de Kotlin! Podés consultar el curso completo desde este link que te dejo acá. Podés seguirme por LinkedIn o Twitter si querés estar al tanto de las próximas publicaciones.
Si echamos la vista atrás, hemos aprendido a declarar variables separadas que siempre implicaban un único valor. Por ejemplo un nombre, apellido, una edad, una localización. Cuando aprendemos estos conceptos, empieza a surgir inmediatamente una pregunta: ¿qué pasa si quiero una lista de enteros o de strings? Para esto existe un concepto milenario llamado arreglos (Arrays en inglés).
Si volvemos a mirar atrás, recordaremos que trabajamos con el tipo CharSequence el cual estaba compuesto por un array de variables tipo Char. Si bien vamos a entender la utilidad en un futuro cercano (cuando trabajemos con bucles e iteraciones), es importante aclarar que en cualquier lenguaje de programación, un String (recordemos que String hereda de CharSequence) siempre es una cadena de caracteres, que es lo mismo que decir “array de chars”. Esto tiene un motivo fundamental en el tratamiento de los strings, pero lo veremos más adelante.
¡Empecemos!
Vamos a retomar el ejemplo que trabajamos en la clase anterior: la consola nos pregunta un nombre y nosotros lo devolvemos en pantalla:
fun main(args: Array<String>) {
print("Como es tu nombre? > ")
val name = readLine()?.filter { it.isDigit().not() }?.toLowerCase()
println("Tu nombre es $name")
}
Ya sabemos que name es de tipo String Nullable, por lo cual tenemos una cadena de caracteres. Vamos a imprimir solo el primer caracter, accediendo a su posición 0:
println("El primer caracter de tu nombre es ${name?.get(0)}")
Por lo cual, si escribimos “Tom”, la consola nos devolverá “El primer caracter de tu nombre es T”. Los arrays siempre comienzan con la posición (o indice) cero, por lo cual si le pedimos la segunda posición (get(1)), nos devolverá “o”. Esto puede resultar confuso al inicio, pero luego nos terminaremos acostumbrando. La mayoría de los lenguajes de programación (salvo el maldito Lua) utilizan los arreglos en el indice cero.
Ahora vamos a un ejemplo más concreto: vamos a crear una lotería, donde tenemos 10 números almacenados en un array. Cuando se ejecute la aplicación, queremos que arroje un número al azar. Entonces en primera instancia debemos declarar nuestra variable:
val numbers: Array<Int> = Array<Int>(10) { 1 }
Declaramos una variable inmutable “numbers” de tipo Array que a su vez va a almacenar valores de tipo Int. En el futuro veremos por qué existe “”, pero si quieres tomar la iniciativa, puedes investigar Generics. El primer parámetro de la inicialización indica el tamaño del arreglo (10 elementos) y el segundo (determinado por una lambda expression) indica el valor incial, el cual es obligatorio.
Vamos a incluir tres valores más. Recuerda que empezamos por el indice 1 porque el primer valor ya fue declarado:
numbers[1] = 4
numbers[2] = 6
numbers[3] = 9
Podríamos imprimir el array en pantalla para ver el estado actual del mismo:
println(numbers.contentToString())
output: [1, 4, 6, 9, 1, 1, 1, 1, 1, 1]
Utilizamos el método contentToString porque nos permite tener una representación más visual del array. Si intentáramos imprimir el array como hacíamos antes, solo nos devolvería su referencia a memoria (tema muy interesante que veremos en un futuro muy lejano).
Como podemos ver, la estructura de nuestro array se compone de 10 elementos, los cuales se llenaron del valor “1” donde no le especificamos nada. Esto es porque si definimos un tamaño inicial, nuestro arreglo tiene que estar lleno. A fin de cuentas, estamos asignando un espacio en memoria y determinando 10 posiciones para el mismo: no podemos dejarlo vacío.
El problema es que empieza a escalar la complejidad: esto nos obliga a completar todos los valores y necesitaríamos unas seis lineas más, lo cual se vuelve tedioso. Piensa que en un ejemplo más real quizá tengas arreglos de cien, o incluso mil posiciones. Es inviable.
Recordemos que uno de los paradigmas en los que Kotlin se sostiene es el de Programación Funcional. Esto siginifica que podemos crear ese arreglo de un modo mucho más sencillo y acotado:
val numbers: Array<Int> = arrayOf(1, 4, 6, 9)
El método arrayOf reemplaza al constructor de arreglos y nos abstrae de las complejidades del mismo. Si imprimimos la variable numbers, notaremos que ya no completa con “1” el resto de las posiciones.
[1, 4, 6, 9]
Esto se da porque en el momento que asignamos los valores con arrayOf, también se define su tamaño: la magia existe, señores. Siguiendo el ejemplo, vamos a incluir los números restantes:
val numbers = arrayOf(1, 4, 6, 9, 15, 30, 45, 60, 78, 90)
Pueden notar que quité el tipo en la variable numbers. Esto es porque, por inferencia, arrayOf lo termina definiendo.
Vamos a mezclar un poco las cosas
Ya tenemos nuestros números definidos para la lotería; ahora nos queda la parte aleatoria: sacar un número al azar. Hay muchas maneras interesantes de hacer esto y las veremos en detalle.
Queremos tomar un valor de los 10, por lo cual existe un método que nos permite elegir un número aleatorio: Random.
val n = Random.nextInt(0, 9)
println(numbers[n])
Creamos una variable “n”, la cual va a contener un indice aleatorio que utilizaremos para pasarle al arreglo “numbers”. El método nextInt de Random contiene dos parámetros que se explican a si mismos: el primero es el valor incial, el segundo el final. El famoso “desde aquí, hasta allá”. Por lo cual tomamos un valor de cero a diez y lo “randomizamos”.
Es importante notar que el valor final es 9 y no 10. Esto es porque nuestro array comienza en cero y posee diez elementos. Por lo cual si contabilizamos los mismos, estos irán del cero al nueve.
Hagamos un pequeño cambio: el valor inicial siempre va a ser cero, eso lo vimos más arriba; pero el final no debería ser fijo. De hecho, tenemos un atributo que nos permite saber el tamaño del array:
val n = Random.nextInt(0, numbers.size - 1)
El atributo size nos devolverá el tamaño del arreglo, el cual es 10. Es por eso que debemos restarle siempre 1, para obtener el valor del indice. Si no restamos este valor, en algun momento nuestro valor aleatorio será 10 y nos devolverá una excepción IndexOutOfBoundsException, también llamada “fui a buscar un indice donde Cristo perdió las chanclas”.
El método Shuffle
Si bien esta solución es funcional, no nos servirá para casos mas complejos: ¿qué ocurre si quiero dos ganadores en vez de uno? Debería repetir la operación, quizá guardarla en otra variable. Esto empieza a perder escalabilidad, porque si necesitamos diez o cien ganadores, se puede volver un infierno.
Para esto voy a usar algo que implementa Spotify cuando queremos que nuestra lista musical se reproducta aleatoriamente: el método Shuffle.
numbers.shuffle()
println(numbers[0])
El método shuffle mezcla el array: nuestro primer valor siempre va a ser distinto porque esta cambiando todas las posiciones. Se utiliza sin asignarlo a nada porque modifica el valor del array en si mismo. Para dar más claridad, voy a utilizar el método contentToString un par de veces luego de shuffle:
numbers.shuffle()
println(numbers.contentToString())
numbers.shuffle()
println(numbers.contentToString())
numbers.shuffle()
println(numbers.contentToString())
output:
[9, 90, 1, 6, 30, 60, 45, 78, 4, 15]
[78, 60, 4, 90, 1, 9, 6, 30, 45, 15]
[4, 30, 1, 45, 60, 78, 6, 90, 15, 9]
Como ven, cada salida es completamente distinta. Entonces si queremos, por ejemplo, decretar dos números ganadores, simplemente accedería a la primer y segunda posición:
println("El ganador #1 es ${numbers[0]}")
println("El ganador #2 es ${numbers[1]}")
Notarán que ahora tenemos mucha mas flexibilidad a la hora de trabajar con los elementos.
Conclusiones
Intenté demostrar el uso de los arreglos desde un lugar donde no se implementa todo su potencial porque todavía nos queda trabajar con ciertos conceptos que le darán un propósito mayor a este tipo de datos. Sin embargo, entender como funcionan sus atributos y sus métodos, será fundamental para las siguientes clases.
Espero que hayan disfrutado el artículo, no olviden compartirlo y ¡nos vemos en el próximo!
Posted on December 13, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.