Mapas en Go
Tabla de contenido
Los mapas son una estructura de datos muy potente, ya que proporcionan una forma de almacenar y recuperar datos a partir de claves.
Son muy utilizados en multitud de áreas, desde el desarrollo web hasta el procesamiento de datos, y son una parte esencial del conjunto de herramientas de cualquier programador Go.
En este artículo, vamos a echar un vistazo más de cerca a cómo funcionan los mapas en Go y cómo los puedes utilizar para resolver problemas del día a día. ¡Así que ajústate el cinturón y comencemos a descubrir el mundo de los mapas en Go!
Añadir un elemento a un mapa #
Para añadir un elemento a un mapa en Go, puedes utilizar el operador de asignación corta :=
:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
mymMap[3] = "three"
fmt.Println(m)
Resultado:
map[0:zero 2:two 3:three]
En este ejemplo, se crea un mapa m
con algunos pares clave-valor. Después añadimos un elemento al mapa con la clave 3
y el valor three
.
Eliminar una clave de un mapa #
Para eliminar una clave de un mapa en Go, puede utilizar la función delete
:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
delete(m, 1)
fmt.Println(m)
Resultado:
map[0:zero 2:two]
En este ejemplo, la función delete
se utiliza para eliminar la clave 1
y su valor asociado del mapa m
.
Nota
: Eliminar una clave que no está en el mapa
no tiene ningún efecto
y no produce ningún error
.
Comprobar si un mapa contiene una clave #
Para comprobar si un mapa contiene una clave en Go, puedes probar el siguiente ejemplo:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
v, exists := m[2]
if exists {
fmt.Println(v)
} else {
fmt.Println(exists)
}
Resultado:
two
Si m[2]
se cambia por m[3]
el resultado será:
false
v
es el valor asociado a la clave en el mapa, y exists
es un booleano que indica si la clave está presente en el mapa.
Si la clave está en el mapa, exists
será true
y v
será el valor asociado a la clave.
Si la clave no está en el mapa, exists
será false
y value
será un valor de tipo cero en los valores almacenados en el mapa.
Iteración sobre las claves de un mapa #
Para iterar sobre las claves de un mapa en Go, puedes utilizar un bucle for para recorrer los pares clave-valor del mapa. Un ejemplo:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
for key := range m {
fmt.Println(key)
}
Resultado:
0
1
2
En este ejemplo, el bucle for iterará sobre todos los pares clave-valor en m
, y las claves se imprimirán una a una.
En Go, el mapa integrado no proporciona un orden garantizado para sus claves. Si necesitas un mapa ordenado consulta la sección Mapa ordenado
de este artículo.
Obtener un slice de claves de un mapa #
Para obtener un slice de claves de un mapa en Go, puedes utilizar un bucle for
para iterar sobre los pares clave-valor en el mapa y añadir las claves a un slice:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
keys := make([]int, 0, len(m))
for key := range m {
keys = append(keys, key)
}
fmt.Println(keys)
Resultado:
[0 1 2]
En este ejemplo, keys
será un slice de strings que contendrá las keys
de m
.
Una nota sobre la función make #
La función make
se utiliza para crear slices, mapas y canales en Go.
En este ejemplo make
se utiliza para crear un slice vacío
con capacidad
igual al número de elementos del mapa, para evitar el redimensionamiento repetido
del slice a medida que se añaden claves.
El primer argumento de make
es el tipo de objeto
que quieres crear, el segundo argumento es la longitud
, y el tercer argumento (opcional) es la capacidad
.
En este caso, las claves del slice se crean con una longitud de 0
y una capacidad de len(m)
ya que la intención es llenar el slice con las claves del mapa m
.
Para ser eficaz en Go, es importante minimizar las asignaciones de memoria
.
Ordenamiento de mapas #
Si necesita que las claves se procesen en un orden específico, puedes ordenar primero el slice de claves y después iterar sobre las claves ya ordenadas:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
keys := make([]int, 0, len(m))
for key := range m {
keys = append(keys, key)
}
// Sustituye esto por tu tipo: sort.Strings para strings
sort.Ints(keys)
for _, key := range keys {
fmt.Printf("%v: %v\n", key, m[key])
}
Resultado:
0: zero
1: one
2: two
En este ejemplo, se crea un slice keys
y se rellena con las claves del mapa m
.
A continuación, se utiliza la función sort.Ints
para ordenar el slicekeys
.
Por último, se utiliza un bucle para iterar sobre el ya ordenado slice de keys
y se imprimen los pares clave-valor del mapa m
.
Mapa ordenado #
En Go, el mapa
integrado no proporciona un orden garantizado para sus claves.
Sin embargo, si necesita un mapa ordenado puede utilizar go-ordered-map
el cual proporciona una implementación de un mapa ordenado en Go, con funciones para insertar, eliminar y acceder a elementos ordenados.
import "github.com/elliotchance/orderedmap/v2"
func main() {
m := orderedmap.NewOrderedMap[int, string]()
m.Set(0, "zero")
m.Set(1, "one")
m.Set(2, "two")
m.Delete(0)
for _, key := range m.Keys() {
value, _ := m.Get(key)
fmt.Println(key, value)
}
}
Resultado:
0 zero
2 two
Convertir un mapa a Json #
Para convertir un mapa Go en una string de JSON, puedes utilizar el paquete encoding/json
:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
jsonBytes, err := json.Marshal(m)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(jsonBytes))
Resultado:
{"0":"zero","1":"one","2":"two"}
En este ejemplo, se utiliza la función json.Marshal
para convertir el mapa m
a una string de JSON.
La variable jsonBytes
almacenará la representación de la string de JSON correspondiente al mapa, la cual podrá imprimirse o grabarse en un fichero según sea necesario.
Si se produce un error durante la conversión, la variable err
se asignará a un valor no nulo y se imprimirá el mensaje de error.
Comparar dos mapas #
Para comparar si dos mapas son iguales en Go, puedes utilizar el paquete reflect
para comparar los valores almacenados en ellos:
m1 := map[int]string{0: "zero", 1: "one", 2: "two"}
m2 := map[int]string{0: "zero", 1: "one", 2: "two"}
if reflect.DeepEqual(m1, m2) {
fmt.Println("m1 y m2 son iguales")
} else {
fmt.Println("m1 y m2 no son iguales")
}
Result:
m1 y m2 son iguales
La función reflect.DeepEqual
se utiliza para comparar los valores almacenados en los mapas, y determinar si son iguales o no.
Si los valores son iguales, reflect.DeepEqual
devuelve true
, y si los valores no son iguales, devuelve false
.
Vaciar un mapa #
Para limpiar un mapa en Go, basta con asignar un nuevo mapa vacío a la variable map ya existente:
m := map[int]string{0: "zero", 1: "one", 2: "two"}
fmt.Println("Antes de limpiar:", m)
// Limpia el mapa
m = make(map[int]string)
fmt.Println("Después de limpiar:", m)
Resultado:
Antes de limpiar: map[0:zero 1:one 2:two]
Después de limpiar: map[]
En este ejemplo, se crea un mapa m
y se rellena con pares clave-valor.
A continuación, se limpia el mapa asignando un nuevo mapa vacío creado con make(map[int]string)
sobre la variable m
.
Slice a Mapa #
Puedes convertir un slice en un mapa en Go iterando sobre el slice y añadiendo cada elemento como un par clave-valor del mapa:
list := []int{0, 1, 2, 3}
m := make(map[int]bool)
for _, value := range list {
m[value] = true
fmt.Println("Slice:", list)
fmt.Println("Mapa:", m)
Result:
Slice: [0 1 2 3]
Mapa: map[0:true 1:true 2:true 3:true]
En este ejemplo, se crea un slice m
con algunos valores del tipo string.
A continuación, el slice se convertirá en un mapa utilizando un bucle for
que iterará el slice y añadirá cada elemento como una clave en el mapa cuyo valor será true
.
La función make
se utiliza para crear un nuevo mapa del tipo map[string]bool
.
Invertir un mapa #
El propósito de invertir un mapa es intercambiar
las claves por los valores.
Por ejemplo, un mapa de claves de tipo número
y valores de tipo string
se invertirá a un mapa de claves de cadtipo stringena
y valores de tipo número
.
¿Por qué es esto útil? Si tomamos como ejemplo un mapa de IDs y nombres, podemos acceder a los nombres con los IDs, pero ¿qué pasa si queremos acceder a los IDs con los nombres?
Tendríamos que iterarlo cada vez
que quisiéramos hacer una búsqueda, lo cual no es eficiente, lo que podemos hacer en su lugar es iterarlo una vez
.
Veamos dos ejemplos:
Un tipo fijo #
m := map[int]string{0: "zero", 1: "one", 2: "two"}
rMap := make(map[string]int)
for key, value := range m {
rMap[value] = key
}
fmt.Println("Mapa original:", m)
fmt.Println("Mapa invertido:", rMap)
Result:
Mapa original: map[0:zero 1:one 2:two]
Mapa invertido: map[one:1 two:2 zero:0]
Este código muestra cómo invertir un mapa en Go creando uno nuevo con las claves y valores intercambiados, el nuevo mapa tendrá los mismos valores que el mapa original, pero con claves y valores invertidos.
Esto es como lo hace::
Se crea un mapa m
con algunos pares clave-valor. Luego se invierte el mapa mediante un bucle for
que itera sobre el mapa original, intercambiando
las claves y los valores en cada iteración.
La función make
se utiliza para crear un nuevo mapa con map[string]int
.
Múltiples tipos #
func main() {
m := map[int]string{0: "zero", 1: "one", 2: "two"}
rMap := reverseMap(m).(map[string]int)
fmt.Println("Mapa original:", m)
fmt.Println("Mapa invertido:", rMap)
}
func reverseMap(m any) any {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
return nil
}
mType := reflect.MapOf(v.Type().Elem(), v.Type().Key())
toReturn := reflect.MakeMap(mType)
for _, k := range v.MapKeys() {
toReturn.SetMapIndex(v.MapIndex(k), k)
}
return toReturn.Interface()
}
Result:
Mapa original: map[0:zero 1:one 2:two]
Mapa invertido: map[one:1 two:2 zero:0]
En este ejemplo, la función reverseMap
toma como argumento un mapa con claves y valores de tipo any
, y devuelve un mapa invertido con las mismas claves y valores. Esto permite que la función se pueda emplear con mapas de distintos tipos.
El bucle for
dentro de la función reverseMap
se utiliza para iterar sobre el mapa original, invirtiendo las claves y los valores en cada iteración.
La función main
crea un mapa m
con algunos pares clave-valor, y llama a la función reverseMap
para obtener ese mapa invertido.
Nota
: En el ejemplo tenemos que escribir de nuevo el tipo de dato
del resultado para no obtener un tipo any
:
m2 := reverseMap(m).(map[string]int)
El uso de reflect
es generalmente bastante peligroso y debes asegurarte de saber lo que estás haciendo si el mapa contiene objetos anidados u otras irregularidades.