Ir al contenido

Mapas en Go

·9 mins
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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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.