Arrays, Slices y Maps en Go: Estructuras de Datos Fundamentales

Introduccion: colecciones de datos en Go
Bienvenido a la parte 5 de nuestro curso de Go para principiantes. Hasta ahora hemos trabajado con variables individuales, pero en la programacion real casi siempre necesitamos manejar grupos de datos. Go ofrece tres estructuras fundamentales para organizar colecciones: arrays, slices y maps.
A diferencia de otros lenguajes como Python o JavaScript, Go hace una distincion clara entre arrays (tamano fijo) y slices (tamano dinamico). Entender esta diferencia es clave para escribir codigo Go eficiente. En este articulo aprenderemos cada estructura con ejemplos practicos y funcionales.
Arrays: colecciones de tamano fijo
Un array en Go es una coleccion de elementos del mismo tipo con un tamano fijo que se define al momento de declararlo. Una vez creado, no puedes cambiar su tamano.
Declaracion y uso basico
1package main
2
3import "fmt"
4
5func main() {
6 // Declarar un array de 5 enteros (se inicializan en 0)
7 var numeros [5]int
8 fmt.Println(numeros) // [0 0 0 0 0]
9
10 // Asignar valores por indice
11 numeros[0] = 10
12 numeros[1] = 20
13 numeros[4] = 50
14 fmt.Println(numeros) // [10 20 0 0 50]
15
16 // Declarar e inicializar al mismo tiempo
17 frutas := [3]string{"manzana", "platano", "naranja"}
18 fmt.Println(frutas) // [manzana platano naranja]
19
20 // Dejar que Go cuente los elementos con [...]
21 colores := [...]string{"rojo", "verde", "azul", "amarillo"}
22 fmt.Println(colores) // [rojo verde azul amarillo]
23 fmt.Println(len(colores)) // 4
24}
[3]int y [5]int son tipos diferentes y no puedes asignar uno al otro. Por esta razon, en la practica se usan mucho mas los slices.
Acceso a elementos e iteracion
1package main
2
3import "fmt"
4
5func main() {
6 notas := [4]float64{9.5, 8.0, 7.5, 10.0}
7
8 // Acceso por indice (empieza en 0)
9 fmt.Println("Primera nota:", notas[0]) // 9.5
10 fmt.Println("Ultima nota:", notas[3]) // 10.0
11
12 // Iterar con for clasico
13 for i := 0; i < len(notas); i++ {
14 fmt.Printf("Nota %d: %.1f\n", i+1, notas[i])
15 }
16
17 // Iterar con range (forma idiomatica)
18 suma := 0.0
19 for _, nota := range notas {
20 suma += nota
21 }
22 promedio := suma / float64(len(notas))
23 fmt.Printf("Promedio: %.2f\n", promedio) // 8.75
24}
Slices: arrays dinamicos
Un slice es una referencia a una seccion de un array subyacente. A diferencia de los arrays, los slices tienen tamano dinamico y son la estructura que mas usaras en Go para colecciones ordenadas.
Crear slices
1package main
2
3import "fmt"
4
5func main() {
6 // Forma 1: literal de slice (sin tamano)
7 frutas := []string{"manzana", "platano", "naranja"}
8 fmt.Println(frutas) // [manzana platano naranja]
9
10 // Forma 2: con make(tipo, longitud, capacidad)
11 numeros := make([]int, 3, 10)
12 fmt.Println(numeros) // [0 0 0]
13 fmt.Println(len(numeros)) // 3 (longitud actual)
14 fmt.Println(cap(numeros)) // 10 (capacidad maxima antes de reasignar)
15
16 // Forma 3: slice vacio
17 var vacio []int
18 fmt.Println(vacio) // []
19 fmt.Println(vacio == nil) // true (un slice nil es valido)
20 fmt.Println(len(vacio)) // 0
21}
make() es la forma recomendada cuando conoces el tamano aproximado que tendra el slice. Esto evita reasignaciones innecesarias de memoria y mejora el rendimiento.
Internos del slice: longitud y capacidad
Para entender bien los slices, necesitas conocer sus tres componentes internos: un puntero al array subyacente, la longitud (len) y la capacidad (cap). La longitud es el numero de elementos que contiene, y la capacidad es el espacio disponible antes de necesitar reasignar memoria.
1package main
2
3import "fmt"
4
5func main() {
6 // Crear un slice con make
7 s := make([]int, 3, 5)
8 fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
9 // len=3 cap=5 [0 0 0]
10
11 // Agregar elementos con append
12 s = append(s, 10)
13 fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
14 // len=4 cap=5 [0 0 0 10]
15
16 s = append(s, 20)
17 fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
18 // len=5 cap=5 [0 0 0 10 20]
19
20 // Al exceder la capacidad, Go crea un nuevo array mas grande
21 s = append(s, 30)
22 fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
23 // len=6 cap=10 [0 0 0 10 20 30]
24 // La capacidad se duplico automaticamente
25}
make() con la capacidad correcta para evitar copias innecesarias.
Operaciones con slices
Go ofrece varias operaciones para manipular slices. Veamos las mas importantes: rebanado, copia y eliminacion de elementos.
Rebanado (slicing)
1package main
2
3import "fmt"
4
5func main() {
6 numeros := []int{10, 20, 30, 40, 50, 60, 70}
7
8 // slice[inicio:fin] - desde inicio hasta fin-1
9 fmt.Println(numeros[1:4]) // [20 30 40]
10 fmt.Println(numeros[:3]) // [10 20 30] (desde el inicio)
11 fmt.Println(numeros[4:]) // [50 60 70] (hasta el final)
12 fmt.Println(numeros[:]) // [10 20 30 40 50 60 70] (copia completa)
13}
Copiar slices
1package main
2
3import "fmt"
4
5func main() {
6 original := []int{1, 2, 3, 4, 5}
7
8 // CUIDADO: esto NO copia, ambos apuntan al mismo array
9 referencia := original
10 referencia[0] = 99
11 fmt.Println(original) // [99 2 3 4 5] (se modifico!)
12
13 // Forma correcta: usar copy()
14 original = []int{1, 2, 3, 4, 5}
15 copia := make([]int, len(original))
16 copy(copia, original)
17 copia[0] = 99
18 fmt.Println(original) // [1 2 3 4 5] (no se modifico)
19 fmt.Println(copia) // [99 2 3 4 5]
20}
Eliminar elementos
1package main
2
3import "fmt"
4
5func main() {
6 nombres := []string{"Ana", "Luis", "Maria", "Carlos", "Pedro"}
7
8 // Eliminar el elemento en el indice 2 ("Maria")
9 indice := 2
10 nombres = append(nombres[:indice], nombres[indice+1:]...)
11 fmt.Println(nombres) // [Ana Luis Carlos Pedro]
12
13 // Agregar multiples elementos
14 nombres = append(nombres, "Sofia", "Diego")
15 fmt.Println(nombres) // [Ana Luis Carlos Pedro Sofia Diego]
16}
append(s[:i], s[i+1:]...) no preserva el orden original si usas la version optimizada. La version mostrada aqui si preserva el orden pero es un poco mas lenta en slices grandes.
Maps: diccionarios clave-valor
Un map es una coleccion de pares clave-valor, similar a un diccionario en Python o un objeto en JavaScript. Las claves deben ser de un tipo comparable (string, int, etc.) y los valores pueden ser de cualquier tipo.
Declaracion y operaciones CRUD
1package main
2
3import "fmt"
4
5func main() {
6 // Crear un map con make
7 edades := make(map[string]int)
8
9 // CREATE: agregar elementos
10 edades["Ana"] = 25
11 edades["Luis"] = 30
12 edades["Maria"] = 28
13 fmt.Println(edades) // map[Ana:25 Luis:30 Maria:28]
14
15 // READ: leer un valor
16 fmt.Println("Edad de Ana:", edades["Ana"]) // 25
17
18 // UPDATE: actualizar un valor
19 edades["Ana"] = 26
20 fmt.Println("Nueva edad de Ana:", edades["Ana"]) // 26
21
22 // DELETE: eliminar un elemento
23 delete(edades, "Luis")
24 fmt.Println(edades) // map[Ana:26 Maria:28]
25
26 // Literal de map (declarar e inicializar)
27 capitales := map[string]string{
28 "Mexico": "CDMX",
29 "Argentina": "Buenos Aires",
30 "Colombia": "Bogota",
31 }
32 fmt.Println(capitales)
33}
El idioma comma-ok
Cuando lees un valor de un map con una clave que no existe, Go devuelve el valor cero del tipo (0 para int, "" para string). Para saber si una clave realmente existe, usa el patron comma-ok:
1package main
2
3import "fmt"
4
5func main() {
6 edades := map[string]int{
7 "Ana": 25,
8 "Maria": 28,
9 }
10
11 // Sin comma-ok: no sabes si el 0 es real o no existe
12 fmt.Println(edades["Pedro"]) // 0 (no existe, pero parece un valor)
13
14 // Con comma-ok: verificas si la clave existe
15 edad, existe := edades["Pedro"]
16 if existe {
17 fmt.Println("Edad de Pedro:", edad)
18 } else {
19 fmt.Println("Pedro no esta en el mapa")
20 }
21
22 // Forma compacta (idiomatica en Go)
23 if edad, ok := edades["Ana"]; ok {
24 fmt.Println("Edad de Ana:", edad) // 25
25 }
26}
valor, ok := mapa[clave] cuando necesites verificar si una clave existe. Es una de las convenciones mas importantes en Go y evita errores sutiles.
Iteracion con range
La palabra clave range es la forma idiomatica de iterar sobre arrays, slices y maps en Go. Es similar al for...of de JavaScript o al for...in de Python.
1package main
2
3import "fmt"
4
5func main() {
6 // Range sobre un slice (indice, valor)
7 frutas := []string{"manzana", "platano", "naranja"}
8 for i, fruta := range frutas {
9 fmt.Printf("%d: %s\n", i, fruta)
10 }
11
12 // Si no necesitas el indice, usa _
13 for _, fruta := range frutas {
14 fmt.Println(fruta)
15 }
16
17 // Si solo necesitas el indice
18 for i := range frutas {
19 fmt.Printf("Indice: %d\n", i)
20 }
21
22 // Range sobre un map (clave, valor)
23 edades := map[string]int{"Ana": 25, "Luis": 30, "Maria": 28}
24 for nombre, edad := range edades {
25 fmt.Printf("%s tiene %d anos\n", nombre, edad)
26 }
27}
Estructuras anidadas
En aplicaciones reales, es comun combinar slices y maps para representar datos complejos. Veamos como crear un slice de maps y un map de slices.
1package main
2
3import "fmt"
4
5func main() {
6 // Slice de maps: lista de estudiantes
7 estudiantes := []map[string]string{
8 {"nombre": "Ana", "carrera": "Ingenieria"},
9 {"nombre": "Luis", "carrera": "Medicina"},
10 {"nombre": "Maria", "carrera": "Derecho"},
11 }
12
13 for _, est := range estudiantes {
14 fmt.Printf("%s estudia %s\n", est["nombre"], est["carrera"])
15 }
16
17 // Map de slices: materias por carrera
18 materias := map[string][]string{
19 "Ingenieria": {"Calculo", "Fisica", "Programacion"},
20 "Medicina": {"Anatomia", "Fisiologia", "Bioquimica"},
21 "Derecho": {"Civil", "Penal", "Constitucional"},
22 }
23
24 fmt.Println("\nMaterias de Ingenieria:")
25 for _, materia := range materias["Ingenieria"] {
26 fmt.Println(" -", materia)
27 }
28
29 // Agregar una materia nueva
30 materias["Ingenieria"] = append(materias["Ingenieria"], "Estadistica")
31 fmt.Println("\nMaterias actualizadas:", materias["Ingenieria"])
32}
Ejemplo practico: contador de palabras y rastreador de notas
Vamos a construir dos programas practicos que combinan todo lo aprendido.
Contador de palabras
1package main
2
3import (
4 "fmt"
5 "strings"
6)
7
8func contarPalabras(texto string) map[string]int {
9 contador := make(map[string]int)
10 palabras := strings.Fields(strings.ToLower(texto))
11 for _, palabra := range palabras {
12 contador[palabra]++
13 }
14 return contador
15}
16
17func main() {
18 texto := "go es genial go es rapido y go es simple"
19 resultado := contarPalabras(texto)
20
21 fmt.Println("Frecuencia de palabras:")
22 for palabra, cuenta := range resultado {
23 fmt.Printf(" %-10s: %d\n", palabra, cuenta)
24 }
25}
Rastreador de calificaciones
1package main
2
3import "fmt"
4
5func main() {
6 // Map de slices: notas por estudiante
7 notas := map[string][]float64{
8 "Ana": {9.5, 8.0, 9.0, 10.0},
9 "Luis": {7.0, 6.5, 8.0, 7.5},
10 "Maria": {10.0, 9.5, 9.0, 9.8},
11 }
12
13 // Agregar una nueva nota a Ana
14 notas["Ana"] = append(notas["Ana"], 8.5)
15
16 // Calcular promedios
17 fmt.Println("=== Reporte de Calificaciones ===")
18 for nombre, calificaciones := range notas {
19 suma := 0.0
20 for _, nota := range calificaciones {
21 suma += nota
22 }
23 promedio := suma / float64(len(calificaciones))
24
25 estado := "Aprobado"
26 if promedio < 7.0 {
27 estado = "Reprobado"
28 }
29 fmt.Printf("%-8s | Promedio: %.2f | %s\n", nombre, promedio, estado)
30 }
31
32 // Encontrar al mejor estudiante
33 mejorNombre := ""
34 mejorPromedio := 0.0
35 for nombre, calificaciones := range notas {
36 suma := 0.0
37 for _, nota := range calificaciones {
38 suma += nota
39 }
40 promedio := suma / float64(len(calificaciones))
41 if promedio > mejorPromedio {
42 mejorPromedio = promedio
43 mejorNombre = nombre
44 }
45 }
46 fmt.Printf("\nMejor estudiante: %s (%.2f)\n", mejorNombre, mejorPromedio)
47}
Comments
Sign in to leave a comment
No comments yet. Be the first!