DEV Community

Maximiliano Burgos
Maximiliano Burgos

Posted on

Curso Kotlin | #9. Arrays

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")
}
Enter fullscreen mode Exit fullscreen mode

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)}")
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]}")
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)