🐸Vectores/Arrays, Slices y Maps
Referencias:
Vectores/Arrays →
En programación se conoce vector o arreglo 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).
Al ser de tamaño fijo, los arrays aseguran que se utilice únicamente la cantidad de memoria necesaria, optimizando así el uso de recursos.
La sintaxis es así:
var nombre_array [longitud]tipo
Donde nombre_array
es el nombre del array, longitud
es la cantidad de elementos que puede almacenar el array y tipo
es el tipo de dato que contendrá el
Ejemplo de un Array en GO →
package main
import "fmt"
func main(){
var calificaciones [5]int
calificaciones[0] = 89
calificaciones[1] = 54
calificaciones[2] = 86
calificaciones[3] = 34
calificaciones[4] = 67
fmt.Println(calificaciones) //Salida: [89 54 86 34 67]
}
Diferencia entre arrays y slices en Go
Aunque los arrays y los slices en Go pueden parecer similares a simple vista, hay diferencias clave entre ellos:
Arrays:
Tamaño fijo en el momento de la declaración
Menos flexibles que los slices
Útiles cuando se conoce el tamaño requerido previamente
Slices:
Tamaño dinámico y puede cambiar durante la ejecución del programa
Más flexibles que los arrays
Útiles cuando se desconoce el tamaño requerido previamente o cuando se espera que cambie
A continuación, se muestra un ejemplo de array y slice en Go para ilustrar las diferencias:
var arrayEjemplo [3]int //declaracion del array
arrayEjemplo[0] = 1
arrayEjemplo[1] = 2
arrayEjemplo[2] = 3
sliceEjemplo := []int{1,2,3} //declaracion slice 3 elementos
sliceEjemplo = append(sliceEjemplo, 4) //añadir un nuevo elemento al slice dinamicamente.
Declarar e inicializar un array en una sola línea:
nombreArray := [tamaño]Tipo{valor1, valor2, ..., valorN}
Iteración sobre un array usando bucles for
Para recorrer todos los elementos de un array, puedes utilizar un bucle for
. Go proporciona dos formas de iterar sobre un array: utilizando un bucle for
con índices y utilizando un bucle for
con la declaración range.
Bucle for con índices
colores := [3]string{"rojo", "verde", "azul"} // Declaración e inicialización del array 'colores'
// Iteración sobre el array utilizando un bucle for con índices
for i := 0; i < len(colores); i++ {
fmt.Println(colores[i]) // Imprime cada elemento del array 'colores'
}
Bucle for
con la declaración range
colores := [3]string{"rojo", "verde", "azul"} // Declaración e inicialización del array 'colores'
// Iteración sobre el array utilizando un bucle for con la declaración range
for i, color := range colores {
fmt.Printf("Índice: %d, Color: %s\\n", i, color) // Imprime el índice y el elemento del array 'colores'
}
//Ten en cuenta que si solo necesitas el elemento y no el índice, puedes utilizar el operador guion bajo (_) para ignorar el valor del índice:
for _, color := range colores {
fmt.Println(color) // Imprime cada elemento del array 'colores' sin utilizar el índice
}
En Go, los tres puntos (
...
) en una declaración de un array indican que el tamaño del array debe ser inferido automáticamente a partir de los elementos que le asignes. No es exactamente lo mismo que tener un "tamaño variable", sino que el compilador determina el tamaño del array con base en la cantidad de elementos proporcionados en la lista de inicialización.
edades := [...]int{1, 2, 3, 4, 4, 3, 5}
Array Bidimensionales o multidimensionales →
matriz := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
} // Inicialización del array bidimensional 'matriz' con valores
elemento := matriz[1][2] // Acceso al elemento en la fila 1 (segunda fila) y columna 2 (tercera columna)
fmt.Println(elemento) // Imprime 6
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 recuerdas, 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.
Crear Slices en Go (otra forma de crearlos)
Por ejemplo, para crear un slice de tipo int
con una longitud inicial de 3
elementos y una capacidad máxima de 5
elementos:
mi_slice := make([]int, 3, 5) //en esta se define un min de elemento y un maximo de capacidad de elementos en el slice.
//Por que la clasica que yo ya conocí es asi:
slice_example := []int{34,54,2} // y ya
En este ejemplo, se crea un slice llamado mi_slice
de tipo int
con una longitud inicial de 3
elementos y una capacidad máxima de 5
elementos.
sliceEjemplo := []int{1,2,3} //declaracion slice 3 elementos
sliceEjemplo = append(sliceEjemplo, 4) //añadir un nuevo elemento al slice dinamicamente.
Agregar elementos a un Slice en Go
Para agregar elementos a un slice, se utiliza la función append(). La sintaxis para agregar un elemento a un slice es la siguiente:
nombre_slice = append(nombre_slice, 10)
Longitud y capacidad de Slices en Go
La longitud de un slice se puede obtener utilizando la función len() y la capacidad del slice se puede obtener utilizando la función cap().
Por ejemplo, para obtener la longitud y la capacidad de mi_slice
:
fmt.Println(len(mi_slice)) // Imprime la longitud del slice: 3
fmt.Println(cap(mi_slice)) // Imprime la capacidad del slice: 5
Maps →
Definición de un mapa:
Los mapas se definen usando la palabra clave map
seguida del tipo de dato de la clave y del tipo de dato del valor.
m := map[string]int{
"clave1": 1,
"clave2": 2,
"clave3": 3,
}
Añadir elementos a un mapa:
Se pueden añadir elementos a un mapa utilizando la sintaxis clave:valor
.
m["clave4"] = 4
Recuperar un valor de un mapa:
Se puede recuperar un valor de un mapa utilizando la clave como índice.
valor := m["clave2"]
fmt.Println(valor) // Imprime "2"
Eliminar un elemento de un mapa:
Se puede eliminar un elemento de un mapa utilizando la función delete
.
delete(m, "clave3")
Comprobación de la existencia de una clave:
Se puede comprobar si una clave existe en un mapa utilizando la función len
.
_, ok := m["clave5"]
if ok {
// La clave existe
} else {
// La clave no existe
}
Recorrer un mapa:
Se puede recorrer un mapa utilizando un bucle for
.
for clave, valor := range m {
fmt.Println(clave, valor)
}
Ordenar un mapa:
No es posible ordenar un mapa directamente en GO. Sin embargo, existen diferentes técnicas para obtener un ordenamiento, como convertir el mapa a una lista y luego ordenarla.
Serialización de mapas:
Los mapas se pueden serializar a diferentes formatos como JSON o XML.
json, err := json.Marshal(m)
if err != nil {
panic(err)
}
fmt.Println(string(json)) // Imprime {"clave1":1,"clave2":2,"clave4":4}
1. Arrays en Go:
Un array es una secuencia contigua de elementos del mismo tipo y de tamaño fijo, lo que significa que no puedes cambiar su longitud una vez declarado.
Declaración:
var arr [5]int // Array de 5 enteros arr[0] = 10 // Asignar valor en la primera posición fmt.Println(arr)
Inicialización directa: Puedes inicializar el array al declararlo:
arr := [3]int{1, 2, 3}
fmt.Println(arr)
Características importantes:
El tamaño es parte del tipo, por lo que
[3]int
y[5]int
son tipos diferentes.Los arrays no son dinámicos, por lo que no puedes agregar o quitar elementos.
Acceso y mutabilidad: Los arrays son mutables, lo que significa que puedes modificar sus elementos directamente usando el índice:
arr[1] = 20 fmt.Println(arr)
2. Slices en Go:
Los slices son una abstracción más flexible y poderosa que los arrays. A diferencia de los arrays, los slices tienen un tamaño dinámico y pueden crecer o reducirse.
Declaración:
var s []int // Un slice de enteros sin tamaño fijo
Inicialización: Puedes crear un slice a partir de un array o directamente:
slice := []int{1, 2, 3}
fmt.Println(slice)
Slice a partir de un array:
arr := [5]int{10, 20, 30, 40, 50}
slice := arr[1:4] // Slice que toma elementos de índice 1 al 3
fmt.Println(slice) // [20 30 40]
Capacidad y longitud: Los slices tienen dos propiedades importantes: la longitud (
len
) y la capacidad (cap
):
fmt.Println(len(slice)) // 3 (número de elementos)
fmt.Println(cap(slice)) // 4 (capacidad restante del array subyacente)
La capacidad indica cuántos elementos se pueden agregar antes de que el slice necesite redimensionarse.
Copiar slices: Para copiar un slice en otro
slice2 := make([]int, len(slice)) // Crear un nuevo slice con longitud específica
copy(slice2, slice)
fmt.Println(slice2)
Aquí, make
se usa para crear un slice vacío con una longitud específica.
Cortar slices (slicing) Puedes crear sub-slices dinámicos de un slice:
subSlice := slice[1:3]
fmt.Println(subSlice) // [30 40]
Peculiaridades y optimización de slices →
Compartir memoria, Los slices comparten el mismo array subyacente, por lo que cambiar el valor en un slice afecta a los otros slices creados del mismo array:
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:3]
slice2 := arr[2:4]
slice1[1] = 100
fmt.Println(arr) // [1 2 100 4 5]
fmt.Println(slice2) // [100 4]
Redimensionar slices, El redimensionamiento de un slice puede crear un nuevo array, lo que tiene un costo en rendimiento cuando se manejan grandes cantidades de datos. Es recomendable predefinir la capacidad de un slice si se sabe cuántos elementos se van a almacenar.
Append y redimensionamiento:
slice := []int{1, 2, 3}
fmt.Println(len(slice), cap(slice)) // 3, 3
slice = append(slice, 4)
fmt.Println(len(slice), cap(slice)) // 4, 6 (capacidad aumentada automáticamente)
Reducir un slice: Eliminar elementos de un slice sin perder la referencia
slice := []int{1, 2, 3, 4, 5}
slice = slice[:len(slice)-1] // Eliminar el último elemento
fmt.Println(slice) // [1 2 3 4]
Preguntas para practicar:
¿Cuál es la diferencia entre la longitud y la capacidad de un slice?
¿Qué ocurre cuando modificas un slice que proviene de un array?
Si creas un slice desde un array, ¿modificar el slice afecta el array original? Explica por qué.
Escribe una función que acepte un slice de enteros y devuelva el promedio de esos enteros.
¿Qué hace la función
append()
internamente si se excede la capacidad del slice?
Cuando estés listo, seguimos con el siguiente tema.
Respuestas:
Correcto. La longitud (
len
) es el número de elementos que actualmente contiene el slice, mientras que la capacidad (cap
) es el número de elementos que puede contener antes de que Go redimensione el slice.Parcialmente correcto. Modificar un slice afecta al array original si el slice aún está basado en el array subyacente. Los slices no son independientes del array; al contrario, comparten memoria. Así que, si modificas un slice, esos cambios se reflejan en el array original.
Incorrecto. Los slices no son copias del array original. Si creas un slice a partir de un array, ambos compartirán la misma memoria. Modificar el slice también modifica el array original, ya que ambos están referenciando la misma región de memoria.
Código incorrecto. En tu función, hay algunos problemas:
No has inicializado la variable
suma
.Estás usando índices incorrectos dentro del loop.
Además, la función debería devolver
float64
porque estamos calculando un promedio.
Aquí te dejo la corrección:
func promedio(slice []int) float64 {
suma := 0
for _, valor := range slice {
suma += valor
}
return float64(suma) / float64(len(slice))
}
Incorrecto. Cuando
append()
excede la capacidad del slice, no retorna un error ni unpanic
. En lugar de eso, Go crea un nuevo array con mayor capacidad, copia los elementos del array original a este nuevo array, y luego agrega el nuevo elemento. El slice devuelto apunta a este nuevo array.
Más preguntas →
¿Cómo puedes asegurarte de que un slice tiene suficiente capacidad antes de agregarle nuevos elementos?
¿Cuál es la diferencia entre declarar un slice con
var
y crearlo conmake
?¿Qué ventaja tiene usar
copy()
en slices en lugar de hacer la asignación directa?¿Cómo puedes reducir la capacidad de un slice sin cambiar su longitud actual?
Respuestas:
Correcto, puedes asegurarte de que un slice tenga suficiente capacidad al usar
make()
y especificar la capacidad adecuada. Sin embargo, si no sabes con exactitud cuántos elementos agregarás, puedes optar por sobreasignar capacidad o monitorear el uso deappend()
para manejar redimensionamientos automáticos.Correcto. La diferencia entre
var
ymake()
es fundamental:Usar
var
simplemente declara el slice, pero no le asigna memoria ni capacidad.Usar
make()
sí reserva memoria para el array subyacente y asigna tanto longitud como capacidad, garantizando que el slice esté completamente inicializado y listo para usar.
Incorrecto en parte. El uso de
copy()
no hace que los slices compartan la misma dirección de memoria. De hecho,copy()
crea una copia de los valores en un nuevo espacio en memoria. La ventaja de usarcopy()
es que puedes duplicar los valores de un slice en otro slice sin que ambos compartan la misma memoria, lo que es útil si no quieres que los cambios en un slice afecten al otro.Correcto, puedes reducir la longitud de un slice cambiando sus límites como en el ejemplo que diste. Sin embargo, esto solo afecta la longitud, no la capacidad. Si quieres reducir la capacidad, podrías usar
copy()
para copiar los valores en un nuevo slice con menor capacidad.
Más preguntas →
¿Qué sucede si haces un slice de un array y luego expandes el slice más allá de la capacidad del array original?
Si tienes un slice
A
y creas un nuevo sliceB
a partir de él (sin usarcopy()
), ¿qué sucede cuando modificas el contenido del sliceB
?¿Cómo puedes inicializar un slice de longitud y capacidad cero usando
make()
?¿Cuál es la diferencia en comportamiento entre un slice vacío
[]int{}
y un slicenil
en Go?
Respuestas:
Incorrecto en parte. Si intentas expandir un slice más allá de la capacidad del array original con
append()
, Go creará un nuevo array que pueda acomodar los nuevos elementos, y el slice comenzará a referenciar este nuevo array. El array original permanece sin cambios. No habrá un error, simplemente se asignará un nuevo array en memoria si la capacidad se supera.Correcto, si creas un nuevo slice a partir de otro sin usar
copy()
, ambos slices compartirán el mismo array subyacente. Por lo tanto, modificar uno de los slices afectará al otro, ya que apuntan a la misma memoria.Correcto, has definido un slice con longitud y capacidad cero correctamente con
make([]int, 0, 0)
.Correcto, un slice vacío (
[]int{}
) está inicializado pero no contiene elementos, mientras que un slicenil
no ha sido inicializado en absoluto. Un slicenil
tiene longitud y capacidad cero, pero también su referencia es nula, mientras que el slice vacío ya tiene una referencia asignada.
Conclusión:
Los mapas en GO son una herramienta poderosa para almacenar y recuperar datos de forma eficiente. Dominar las diferentes características de los mapas te ayudará a crear programas más robustos y escalables.
Last updated