Cristhian Villegas
Backend10 min read6 views

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

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

Logo de Go

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

go
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}
Atencion: En Go, el tamano del array es parte de su tipo. Esto significa que [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

go
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

go
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}
Nota: La funcion 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.

go
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}
Tip: Cuando Go necesita mas espacio, duplica la capacidad del slice (para slices pequenos). Por eso, si sabes cuantos elementos tendras, usa 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)

go
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

go
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

go
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}
Atencion: La tecnica de eliminar con 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

go
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:

go
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}
Tip: Siempre usa el patron 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.

go
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}
Atencion: El orden de iteracion en un map no esta garantizado en Go. Cada vez que iteras, el orden puede ser diferente. Si necesitas un orden especifico, debes ordenar las claves primero en un slice.

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.

go
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

go
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

go
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}
Siguiente articulo: En la parte 6 aprenderemos sobre structs, metodos e interfaces en Go. Descubriremos como Go implementa la programacion orientada a objetos sin clases, usando composicion en lugar de herencia.
Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles