6. Arreglos y Slices#

6.1. Arreglos#

En Go los arreglos o arrays son estructuras de datos que almanecenan una cantidad arbitraria de valores del mismo tipo. A nivel de memoria, todos sus elementos se encuentran en posiciones contiguas.

El tamaño de un array es definido al momento de su creación y determina su “tipo”. Es decir, un array de 7 elementos tiene un tipo diferente a un array de 3.

Podemos declarar un array de la siguiente forma:

var numeros [7]int

Aquí creamos una variable de tipo [7]int a la que referenciaremos con el nombre numeros.

La forma de acceder o modificar los valores en las distintas posiciones de un array es por medio del índice que indica la posición, como ya se ha visto en materias anteriores y como en la mayoría los lenguajes de programación (aunque no todos), empezando de 0 hasta el largo menos 1.

numeros[0] = 42
numeros[3] = 1337

fmt.Println(numeros[0] + numeros[3])
1379

En el caso, de un array de 7 elementos, podremos acceder a los elementos en los indices desde el 0 hasta el 6 inclusive. Intentar acceder a un índice fuera de este rango va a causar un error.

numeros[7]
panic: runtime error: index out of range [7] with length 7

Cuando un array es pasado como argumento en una función o método, este pasa por “referencia”. Es decir, que la fución que recibe esa referencia tendrá la libertad de modifcar el contenido de dicho array.

En Go para conocer el largo de un array existe la función len.

len(numeros)
7

Para recorrer un array en Go existe la instrucción range, que genera un iterador sobre el array devolviendo el índice (i) y el valor (v), en cadaiteración del for, veamos un ejemplo:

nombres := [4]string{"Fabián", "Martín", "Valeria", "Santiago"}

for i, v := range nombres {
    fmt.Println(i, "|", v)
}
0 | Fabián
1 | Martín
2 | Valeria
3 | Santiago

6.2. Slices#

Los slices representan secuencias de longitud variable cuyos elementos son del mismo tipo. Un tipo de slice se escribe como []T, donde los elementos son de tipo T; se asemeja a un tipo de array sin tamaño.

Los arreglos y los slices están estrechamente relacionados. Un slice es una estructura de datos ligera que da acceso a una subsecuencia (o quizás a todos) de los elementos de un arreglo, conocido como el arreglo subyacente de la slice. Una slice tiene tres componentes: un puntero, una longitud y una capacidad. El puntero apunta al primer elemento del arreglo accesible a través de la slice, que no es necesariamente el primer elemento del arreglo. La longitud es el número de elementos de la slice; no puede exceder la capacidad, que generalmente es el número de elementos entre el inicio de la slice y el final del arreglo subyacente. Las funciones integradas len y cap devuelven estos valores.

var s []byte
../_images/slice-struct.png

Figura 6.1 Estructura interna de un slice.#

s= make([]byte, 5, 5)
../_images/slice-1.png

Figura 6.2 Slice de longitud 5 y capacidad 5.#

A medida que hacemos slicing de s, observamos los cambios en la estructura de datos del slice y su relación con el arreglo subyacente:

s = s[2:4]
../_images/slice-2.png

Figura 6.3 Slice de longitud 2 y capacidad 3.#

El slicing no copia los datos del slice. En su lugar, crea un nuevo valor de slice que apunta al arreglo original. Esto hace que las operaciones con slices sean tan eficientes como manipular índices de arreglos. Por lo tanto, modificar los elementos (no el slice en sí) de un re-slice modifica los elementos del slice original:

s = s[:cap(s)]
../_images/slice-3.png

Figura 6.4 Slice de longitud 3 y capacidad 3.#

Múltiples slices pueden compartir el mismo array subyacente y pueden referirse a partes superpuestas de ese array. La Figura 4.1 muestra un array de cadenas para los meses del año y dos slices superpuestos de este. El array se declara como:

meses := [12]string{"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio",
    "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"}

El operador slice s[i:j], donde \(0 \leq i \leq j \leq \texttt{cap(s)}\), crea un nuevo slice que hace referencia a los elementos desde i hasta j-1 de las secuencias, que pueden ser una variable de array, un puntero a un array, u otro slice. El slice resultante tiene j-i elementos. Si se omite i, su valor es 0, y si se omite j, su valor es len(s). Así, el slice months[0:12] hace referencia a todo el rango de meses válidos, al igual que el slice months[1:]; el slice months[:] hace referencia a todo el array. Definamos slices superpuestos para el segundo trimestre y el invierno:

t2 := meses[3:6]
invierno := meses[5:8]
fmt.Println("t2 =", t2, "\ninvierno =", invierno)
t2 = [Abril Mayo Junio] 
invierno = [Junio Julio Agosto]
../_images/overlaping-slices.drawio.svg

Figura 6.5 Dos slices sobre el mismo array de meses.#

Hacer slicing más allá de cap(s) causa un pánico, pero hacer slicing más allá de len(s) extiende el slice, por lo que el resultado puede ser más largo que el original:

fmt.Println(invierno[:20])
panic: runtime error: slice bounds out of range [:20] with capacity 7
inviernoSinFin := invierno[:5]
fmt.Println(inviernoSinFin)
[Junio Julio Agosto Septiembre Octubre]

6.2.1. Agregando elementos a un slice#

Para agregar elementos a un slice se utiliza la función append, que toma un slice y uno o más elementos del mismo tipo que el slice y devuelve un nuevo slice que contiene todos los elementos del slice original más los nuevos elementos. Si el slice resultante es mayor que la capacidad del slice original, append crea un nuevo slice que es el doble de grande, copia los elementos del slice original y luego agrega los nuevos elementos:

s := []int{1, 2, 3}
fmt.Println(s, "\nlen =", len(s), "\ncap =", cap(s))
[1 2 3] 
len = 3 
cap = 3
s = append(s, 4, 5)
fmt.Println(s, "\nlen =", len(s), "\ncap =", cap(s))
[1 2 3 4 5] 
len = 5 
cap = 6

Puede darse el caso en el que si tenemos dos slices sobre un mismo array subyacente, al agregar un elemento a uno de los slices, el otro también se vea modificado:

x := make([]int, 0, 4)
x = append(x, 0, 1, 2)

y := x
y = append(y, 3)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2] 
y = [0 1 2 3]
../_images/slice-append-entangled-1.drawio.svg
x = append(x, 4)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4] 
y = [0 1 2 4]
../_images/slice-append-entangled-2.drawio.svg

También si el slice sobre el que agregamos el nuevo elemento, no tiene más capacidad para agregar elementos, se crea un nuevo slice con el doble de capacidad y se copian los elementos del slice original:

y = append(y, 4)

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4] 
y = [0 1 2 4 4]
../_images/slice-append-entangled-3.drawio.svg

es importante notar que el array subyacente ya no es el mismo ya que el slice original fue copiado a un nuevo array, pero el segundo slice sigue apuntando al array subyacenter original. Si modificamos algunos de los valores de y, no va a afectar a x:

y[3] = 3

fmt.Println("x =", x, "\ny =", y)
x = [0 1 2 4] 
y = [0 1 2 3 4]

6.3. Ejercicios#

  1. Escriba una función invertir que invierta un slice. Por ejemplo, el slice [1, 2, 3, 4] invertido sería [4, 3, 2, 1].

  2. Escriba una función rotar que rote un slice en un número n de posiciones. Por ejemplo, el slice [1, 2, 3, 4, 5] rotado en 2 posiciones sería [3, 4, 5, 1, 2].

  3. Escriba una función eliminar que elimine un elemento de un slice. Por ejemplo, el slice [1, 2, 3, 4, 5] eliminando el elemento en la posición 2 sería [1, 2, 4, 5].

  4. Escriba una función eliminarDuplicados que elimine los elementos duplicados de un slice. Por ejemplo, el slice [1, 2, 2, 3, 4, 4, 5] sin duplicados sería [1, 2, 3, 4, 5].