Array y Slice en Go (Golang)

En programación se le denomina vector o formación (en inglés array) a una zona de almacenamiento contiguo que contiene una serie de elementos del mismo tipo, los elementos de la matriz. Desde el punto de vista lógico una matriz se puede ver como un conjunto de elementos ordenados en fila (o filas y columnas si tuviera dos dimensiones).
Wikipedia
Arreglos
Si no conocen los arreglos, recomiendo investigar un poco más al respecto para sacar el mayor provecho de esta publicación, debido a que me limitaré a explicar como funciona específicamente en el lenguaje Go.
Hasta ahora hemos visto tipos de datos sencillos como Números enteros, Decimales, Cadenas de texto y Booleanos; con estos abordamos algunas características del lenguaje. Pero en ocasiones necesitas agrupar valores con cierta relación, por ejemplo, un conjunto de nombres. Basado en lo que hemos aprendido podríamos hacer algo como lo siguiente.
var nombre1 = "Ana"
var nombre2 = "José"
var nombre3 = "Daniel"
var nombre4 = "María"
var nombre5 = "Carlos"Eso podría funcionar, pero quizás necesites 10 valores, o 20, o no conozcan exactamente cuantos valores van a necesitar. En esos casos los arreglos son muy útiles. Retomemos el ejemplo anterior.
var nombres [5]stringnombres[0] = "Ana"
nombres[1] = "José"
nombres[2] = "Daniel"
nombres[3] = "María"
nombres[4] = "Carlos"
Es posible que estés pensando que es prácticamente lo mismo, y ahora hasta escribimos una línea más. En parte tienes razón, en este caso quizás una sintaxis como las siguiente resulte mas practica.
nombres := [5]string{“Ana”, “José”, “Daniel”, “María”, “Carlos”}Supongo que ya estás familiarizado con las declaración corta; en ambos casos estamos declarando una variable con el identificador nombres que contiene un arreglo de 5 elementos de tipo string. Es importante saber que los arreglos en Go sólo pueden contener elementos de un mismo tipo (eventalmente hablaremos de uno muy flexible llamado interface), en este caso cadenas de texto, además que su tamaño es fijo. Veámos un ejemplo para comprobarlo.
package mainimport "fmt"func main() {
var numeros [10]int
fmt.Println(numeros) // [0 0 0 0 0 0 0 0 0 0]
}
Si recuerdan, al declarar una variable Go le da su valor cero como valor inicial, en este caso, al ser un arreglo de 10 elementos de tipo int, se crea un arreglo con 10 ceros, siendo 0 el valor cero, valga la redundancia, de las variables de tipo int; tal como en el primer ejemplo, podemos acceder y modificar cada valor en el Arreglo a través de su indice, el cual comienza a enumerarse desde el 0, es decir, siendo este un arreglo de 10 valores, el indice del último valor sería 9. Veamos un ejemplo de esto.
numeros[0] = 6
numeros[2] = 9
numeros[5] = 18
numeros[9] = 15n := numeros[5]fmt.Println("El valor de n es", n) // El valor de n es 18 fmt.Println(numeros) // [6 0 9 0 0 18 0 0 0 15]
Como pueden ver, a través de la sintaxis de corchetes podemos acceder al valor en la posición correspondiente en el arreglo tanto para asignar un nuevo valor como para realizar una operación con él, como asignarlo a otra variable. Otra característica interesante es que podemos acceder tanto a la longitud de un arreglo como a su capacidad (hablaré un poco mas al respecto en la siguiente parte) haciendo uso de las funciones integradas len y cap respectivamente.
fmt.Println(len(numeros)) // 10
fmt.Println(cap(numeros)) // 10Esto es muy útil, por ejemplo, para acceder al último elemento en un arreglo, sin importar su longitud sabemos que su último indice coincide con el valor de su longitud menos uno, en el caso del presente ejemplo: len(numeros) – 1.
Slices
Ahora que ya sabemos lo que es un array en Go, entender lo que es un slice no debería suponer problema alguno. Si recuerdan, en el apartado anterior aclaraba que los arreglos tienen un tamaño fijo, y pudieron notar que su capacidad y longitud coincidían, por lo posiblemente se estén cuestionando la utilidad de conocer el valor de capacidad.
Los slices pueden verse como arreglos de longitud dinámica, siendo un poco más técnicos, un slice apunta a un array, claro que aún no hablamos de punteros, estoy preparando una publicación específica para ellos, por el momento no se preocupen. Veamos un ejemplo.
nombresArray := [5]string{"Ana", "José", "Daniel", "María", "Carlos"}nombresSlice := nombresArray[0:3]fmt.Println(nombresSlice) // [Ana José Daniel] fmt.Println(len(nombresSlice)) // 3
fmt.Println(cap(nombresSlice)) // 5
Con excepción del nombre el arreglo en la primera línea, es el mismo que vimos antes, la parte interesante es la siguiente línea, a la variable nombresSlice se le asigna una porción de nombresArray; la regla de los indices es la misma, el valor 0 hace referencia a la primera posición en el arreglo, el número 3 que se encuentra después de los dos puntos indican la posición final a la que deseamos acceder, sin embarco, esta no incluye al valor en dicha posición sino al anterior a este; por tal motivo, el valor en el indice 3, María, no se incluye en el nombresSlice.
Si observamos ahora la salida de las últimas dos líneas, notaremos que, a diferencia del ejemplo con arreglos, los valores de longitud y capacidad no son iguales. Es decir, el valor de longitud es el esperado (3), pero la capacidad es de 5. Me parece que esto es más fácil de entender con un ejemplo.
Por una parte estamos conociendo la función append, creo que podrán suponer lo que hace, toma como primer argumento un slice y por segundo a un valor o valores a ser añadidos al slice y retorna un nuevo slice con todos los valores del anterior además del nuevo valor.
Por otra parte notamos que a medida que añadimos elementos al slice su longitud se incrementa pero su capacidad se mantiene hasta que la longitud la supera, llegado a ese punto la capacidad del slice se duplica. Ese es básicamente el comportamiento natural de los slice.
Perfecto, ahora sabemos que podemos crear un slice a partir de un array existente, pero esa no es la única manera, veamos otras opciones.
var numeros1 []int
numeros2 := []int{1, 2, 3, 4, 5, 6}
numeros3 := make([]int, 5)fmt.Println(numeros1) // []
fmt.Println(numeros2) // [1 2 3 4 5 6]
fmt.Println(numeros3) // [0 0 0 0 0]
Analicemos un poco el código anterior, en el primer caso declaramos una variable numeros1 como un slice de elementos int, es muy similar a como declaramos un array en casos anteriores con la única diferencia de que no pasamos ningún valor en los corchetes, por lo que se inicializa como un slice vacío.
En el segundo caso usamos la declaración corta, pasando directamente un grupo de valores entre llaves, nuevamente es muy similar a la declaración de un array con la ausencia de un valor entre los corchetes.
Finalmente tenemos la declaración de un slice utilizando la función make, esta función no solo permite construir slice pero por el momento es suficiente con entender que estamos creando un slice de números enteros con una capacidad inicial de 5, también es posible establecer explícitamente un valor inicial para la longitud del slice pasando otro valor numérico a la función make.
En una publicación pasada conocimos los ciclos en Go, más específicamente el ciclo for y sus variantes. Como en muchos lenguajes de programación los ciclos permiten recorrer los valores de un arreglo, y de un slice en este caso, de manera bastante sencilla.
nombresArray := [5]string{"Ana", "José", "Daniel", "María", "Carlos"}for i := 0; i < len(nombresArray); i++ {
fmt.Println(nombresArray[i])
}// Ana
// José
// Daniel
// María
// Carlos
Nada raro, muy sencillo. Tomamos lo que ya sabemos del ciclo for y lo combinamos con lo que acabamos de aprender de los arreglos/slices y el resultado parece bastante natural.
Inicializamos la variable i con el valor 0, comprobamos la condición, que el valor actual de i sea menor a la longitud del arreglo, realizamos la impresión de cada valor del arreglo accediendo ellos por medio de su indice, el cual introducimos de manera dinámica gracias a la variable i que comenzará en 0 e irá incrementándose hasta que alcance el valor de la longitud del arreglo, 10 en nuestro caso, en ese momento la condición deja de cumplirse y el ciclo termina.
Sin embargo Go nos proporciona una forma aún mas sencilla para recorrer un arreglo/slice, otra variación del ciclo for que también fue explicada con anterioridad, así que me limitaré a mostrar su uso.
nombresArray := [5]string{"Ana", "José", "Daniel", "María", "Carlos"}for i, v := range nombresArray {
fmt.Printf("Indice %d valor %s\n", i, v)
}// Indice 0 valor Ana
// Indice 1 valor José
// Indice 2 valor Daniel
// Indice 3 valor María
// Indice 4 valor Carlos
Esta variación del ciclo for que me gusta llamar for-range, es muy parecida a lo que se conoce como foreach en otros lenguajes de programación. El resultado es el mismo, prácticamente, pero no requiere conocer la longitud el arreglo, el ciclo comenzará en el indice 0 del arreglo y lo recorrerá hasta el final. Otro detalle importante es que el range retorna dos valores, el primero es el indice y el segundo el valor.
Originally published at steemit.com on September 4, 2018.
