mapas-diccionarios-clave-valor-go

Maps en Go: diccionarios clave-valor y patrón comma ok

  • 4 min

Un mapa en Go es una colección clave-valor para buscar datos por un identificador, como un diccionario donde cada clave apunta a un valor concreto.

Hasta ahora hemos visto cómo guardar datos en fila india con arrays y slices. Funcionan bien si sabemos el orden o el índice numérico. Para buscar por un DNI, un nombre o cualquier otra clave comparable usamos mapas.

En otros lenguajes se llaman diccionarios o hash maps. El concepto es el mismo: una estructura sin orden de iteración garantizado que asocia una clave única con un valor.

Qué es un mapa en Go

Un mapa en Go es una referencia a una tabla hash. La sintaxis para definirlo es: map[TipoClave]TipoValor.

  • Tipo de la clave: puede ser cualquier tipo comparable, como booleanos, números, strings, canales, punteros y determinados arrays, estructuras e interfaces. No puede ser un slice, un mapa ni una función.
  • Tipo del valor: puede ser cualquier tipo, incluso otro mapa.

Declaración e inicialización

Aquí hay una trampa en la que caen casi todos los principiantes.

Si declaras un mapa solo con var, obtienes un mapa nil. Un mapa nil se comporta de forma curiosa:

  1. Puedes leer de él (te devuelve ceros).
  2. No puedes asignar una entrada: provoca un panic.
package main

import "fmt"

func main() {
    // ❌ FORMA PELIGROSA
    var colores map[string]string // Es nil
    // colores["rojo"] = "#FF0000" // ERROR: panic: assignment to entry in nil map

    // ✅ FORMA CORRECTA 1: make()
    // Inicializa el mapa y reserva memoria
    puntuaciones := make(map[string]int)
    puntuaciones["Luis"] = 10

    // ✅ FORMA CORRECTA 2: Literal
    // Muy útil para valores iniciales
    capitales := map[string]string{
        "España":  "Madrid",
        "Francia": "París",
        "Italia":  "Roma", // ¡Ojo a la coma final obligatoria!
    }

    fmt.Println(capitales)
}
Copied!

Usa make(map[...]) o un literal si vas a escribir en el mapa. Un mapa nil sigue siendo útil como valor vacío de solo lectura.

Operaciones básicas

Trabajar con mapas es muy intuitivo:

Insertar o actualizar

No hay diferencia. Si la clave no existe, la crea. Si existe, la machaca.

m := make(map[string]int)
m["edad"] = 30
m["edad"] = 31 // Actualiza
Copied!

Leer un valor

valor := m["edad"]
Copied!

Borrar una clave

Go tiene una función nativa (built-in) llamada delete.

delete(m, "edad")
Copied!

Curiosidad: Si intentas borrar una clave que no existe, no pasa nada (no da error).

El valor cero y el patrón comma, ok

¿Qué pasa si intentas leer una clave que no existe? En otros lenguajes te daría una excepción o null. En Go te devuelve el Zero Value del tipo de valor.

puntuaciones := map[string]int{"Ana": 10}

nota := puntuaciones["Pedro"] // Pedro no existe
fmt.Println(nota) // Imprime 0 (porque es el zero value de int)
Copied!

Esto es un problema. ¿Pedro tiene un 0 porque suspendió, o tiene un 0 porque no se presentó al examen (no está en el mapa)?

Para distinguir estos casos, usamos la asignación múltiple, conocida como el patrón “Comma Ok”:

valor, ok := mapa[clave]
Copied!
  • valor: El valor almacenado (o cero si no está).
  • ok: Un booleano (true si la clave existe, false si no).
if nota, ok := puntuaciones["Pedro"]; ok {
    fmt.Println("La nota de Pedro es:", nota)
} else {
    fmt.Println("Pedro no está en la lista")
}
Copied!

Este patrón es habitual en Go. Úsalo cuando necesites distinguir entre una clave ausente y un valor cero almacenado.

Iterar sobre mapas

Para recorrer un mapa usamos el bucle for con range, igual que en los slices, pero obtenemos clave y valor.

menu := map[string]float64{
    "Café": 1.50,
    "Tostada": 2.00,
    "Zumo": 3.00,
}

for plato, precio := range menu {
    fmt.Printf("%s cuesta %.2f\n", plato, precio)
}
Copied!

El orden no está garantizado

Esto es crítico. Los mapas en Go no están ordenados. Cada vez que ejecutes el for range anterior, es posible que los elementos salgan en un orden distinto. Simplemente no dependas de ese orden.

Si necesitas iterar en orden (por ejemplo, alfabético), debes:

  1. Extraer las claves a un slice.
  2. Ordenar ese slice.
  3. Iterar el slice y buscar en el mapa.

Mapas y datos compartidos

Al igual que ocurre con los slices, al pasar un mapa a una función se copia un descriptor pequeño, pero ambos apuntan a la misma tabla interna. Si la función lo modifica, el mapa original cambia.

func modificarMapa(m map[string]int) {
    m["nuevo"] = 999
}

func main() {
    miMapa := make(map[string]int)
    modificarMapa(miMapa)
    fmt.Println(miMapa) // Imprime map[nuevo:999]
}
Copied!