Maps en Go
Un map es una coleccion desordenada de pares clave-valor. Es el equivalente a diccionarios (Python), HashMap (Java) o objetos (JavaScript).
Declaracion y Creacion
Hay dos formas principales de crear un map:
package main
import "fmt"
func main() {
// Con make
edades := make(map[string]int)
edades["Ana"] = 30
edades["Carlos"] = 25
edades["Diana"] = 28
fmt.Println(edades) // map[Ana:30 Carlos:25 Diana:28]
// Literal
capitales := map[string]string{
"Colombia": "Bogota",
"Peru": "Lima",
"Chile": "Santiago",
}
fmt.Println(capitales)
// Map vacio (no nil)
vacio := map[string]int{}
fmt.Println(len(vacio)) // 0
}
Comma Ok Idiom
Cuando accedes a una clave que no existe, Go retorna el zero value del tipo. Para distinguir entre “existe con zero value” y “no existe”, usa el comma ok idiom:
package main
import "fmt"
func main() {
puntos := map[string]int{
"Ana": 100,
"Carlos": 0, // cero es un valor valido
}
// Sin comma ok: ambos retornan 0
fmt.Println(puntos["Carlos"]) // 0
fmt.Println(puntos["Diana"]) // 0 (no existe)
// Con comma ok: distingue existencia
val, ok := puntos["Carlos"]
fmt.Printf("Carlos: val=%d existe=%t\n", val, ok) // val=0 existe=true
val, ok = puntos["Diana"]
fmt.Printf("Diana: val=%d existe=%t\n", val, ok) // val=0 existe=false
// Patron comun: verificar y actuar
if score, existe := puntos["Ana"]; existe {
fmt.Println("Ana tiene", score, "puntos")
} else {
fmt.Println("Ana no encontrada")
}
}
delete()
delete elimina una clave del map. Si la clave no existe, no hace nada (no causa panic):
package main
import "fmt"
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
fmt.Println(m) // map[a:1 b:2 c:3]
delete(m, "b")
fmt.Println(m) // map[a:1 c:3]
// Eliminar clave inexistente: no hace nada
delete(m, "z")
fmt.Println(m) // map[a:1 c:3]
}
Iterar con range
El orden de iteracion no esta garantizado. Cada ejecucion puede producir un orden diferente:
package main
import (
"fmt"
"sort"
)
func main() {
frutas := map[string]int{
"manzana": 5,
"banana": 3,
"cereza": 8,
"durazno": 2,
}
// Orden no determinista
fmt.Println("=== Orden aleatorio ===")
for clave, valor := range frutas {
fmt.Printf("%s: %d\n", clave, valor)
}
// Para orden determinista: ordenar las claves
fmt.Println("\n=== Orden alfabetico ===")
claves := make([]string, 0, len(frutas))
for k := range frutas {
claves = append(claves, k)
}
sort.Strings(claves)
for _, k := range claves {
fmt.Printf("%s: %d\n", k, frutas[k])
}
}
Maps como Sets
Go no tiene un tipo set nativo. Se usa map[T]bool o map[T]struct{}:
package main
import "fmt"
func main() {
// Set con bool (mas legible)
visitados := map[string]bool{
"Lima": true,
"Bogota": true,
}
if visitados["Lima"] {
fmt.Println("Ya visitaste Lima")
}
if !visitados["Santiago"] {
fmt.Println("No has visitado Santiago")
}
// Set con struct{} (no usa memoria para el valor)
tags := map[string]struct{}{
"go": {},
"rust": {},
}
if _, existe := tags["go"]; existe {
fmt.Println("Tag 'go' presente")
}
// Agregar
tags["python"] = struct{}{}
// Eliminar
delete(tags, "rust")
fmt.Println("Tags:", len(tags))
}
| Enfoque | Memoria | Legibilidad | Verificacion |
|---|---|---|---|
map[T]bool | 1 byte por entrada | Alta | if m[key] |
map[T]struct{} | 0 bytes por valor | Media | _, ok := m[key] |
nil Map vs Empty Map
Un nil map permite lectura pero causa panic al escribir:
package main
import "fmt"
func main() {
// nil map
var m map[string]int
fmt.Println(m == nil) // true
fmt.Println(len(m)) // 0
fmt.Println(m["clave"]) // 0 (zero value, no panic)
// m["clave"] = 1 // PANIC: assignment to entry in nil map
// Empty map: seguro para lectura y escritura
m2 := make(map[string]int)
fmt.Println(m2 == nil) // false
m2["clave"] = 1 // OK
fmt.Println(m2)
}
Regla: siempre inicializa un map con make o literal antes de escribir en el.
Conteo de Frecuencia
Patron clasico usando maps:
package main
import (
"fmt"
"strings"
)
func contarPalabras(texto string) map[string]int {
frecuencia := make(map[string]int)
palabras := strings.Fields(texto)
for _, p := range palabras {
frecuencia[strings.ToLower(p)]++
}
return frecuencia
}
func main() {
texto := "Go es simple Go es rapido Go es concurrente"
freq := contarPalabras(texto)
for palabra, conteo := range freq {
fmt.Printf("%-12s %d\n", palabra, conteo)
}
}
Maps No Son Concurrency-Safe
Acceder a un map desde multiples goroutines sin sincronizacion causa panic. Para uso concurrente usa sync.Map o protege con sync.Mutex:
package main
import (
"fmt"
"sync"
)
func main() {
// sync.Map para uso concurrente
var m sync.Map
// Store (escribir)
m.Store("clave1", 100)
m.Store("clave2", 200)
// Load (leer)
if val, ok := m.Load("clave1"); ok {
fmt.Println("clave1:", val) // 100
}
// LoadOrStore: retorna existente o almacena nuevo
actual, loaded := m.LoadOrStore("clave3", 300)
fmt.Printf("clave3: val=%v yaExistia=%t\n", actual, loaded)
// Delete
m.Delete("clave2")
// Range (iterar)
m.Range(func(key, value any) bool {
fmt.Printf("%v: %v\n", key, value)
return true // continuar iterando
})
}
sync.Map es mas eficiente que map + Mutex solo cuando las lecturas superan ampliamente a las escrituras. Para la mayoria de casos, map con sync.RWMutex es preferible.
Paquete maps (Go 1.21+)
Desde Go 1.21, el paquete maps ofrece utilidades genericas:
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
m := map[string]int{
"go": 1,
"rust": 2,
"python": 3,
}
// Obtener claves y valores como slices
claves := slices.Collect(maps.Keys(m))
valores := slices.Collect(maps.Values(m))
fmt.Println("Claves:", claves)
fmt.Println("Valores:", valores)
// Clonar
clon := maps.Clone(m)
clon["java"] = 4
fmt.Println("Original:", m) // sin "java"
fmt.Println("Clon:", clon) // con "java"
// Comparar
m2 := map[string]int{"go": 1, "rust": 2, "python": 3}
fmt.Println("Iguales:", maps.Equal(m, m2)) // true
// Copiar entries de un map a otro
extra := map[string]int{"zig": 5, "go": 99}
maps.Copy(m, extra) // sobreescribe go=99, agrega zig=5
fmt.Println("Despues de Copy:", m)
// DeleteFunc: eliminar por condicion
maps.DeleteFunc(m, func(k string, v int) bool {
return v > 10
})
fmt.Println("Despues de DeleteFunc:", m)
}
Tipos Validos como Clave
Las claves deben ser comparables (soportar ==). Slices, maps y funciones no son validos como clave:
| Tipo | Valido como clave |
|---|---|
int, float64, bool | Si |
string | Si |
[N]T (array) | Si (si T es comparable) |
struct | Si (si todos los campos son comparables) |
*T (puntero) | Si (compara direccion) |
[]T (slice) | No |
map[K]V | No |
func() | No |
Ejercicios
- Escribe una funcion que invierta un map
map[string]string(valores se convierten en claves) - Implementa
agrupar(personas []Persona, campo func(Persona) string) map[string][]Persona - Crea una funcion que encuentre el primer caracter no repetido en un string usando un map
Resumen
- Maps son colecciones desordenadas de pares clave-valor
- Comma ok idiom (
val, ok := m[key]) para verificar existencia delete(m, key)elimina entradas de forma segura- El orden de iteracion con
rangeno esta garantizado map[T]boolomap[T]struct{}funcionan como sets- Siempre inicializar antes de escribir (nil map causa panic)
- No son concurrency-safe; usar
sync.Maposync.Mutexpara acceso concurrente - Paquete
maps(Go 1.21+) agrega Clone, Equal, Keys, Values