Go incorpora un tipo nativo que implementa tablas hash llamadas map. Es un tipo de datos compuesto por una colección de claves únicas y una colección de valores para cada una de esas claves.
Se puede comparar, por ejemplo, con un diccionario de otros idiomas, que almacena pares clave-valor. A estos valores se accede mediante claves, de la misma forma que a los arrays y cortes como vimos en el post anterior.
Los índices no se limitan a un número como en los arrays o cortes y los elementos no están ordenados, por lo que si imprimimos un mapa devolverá un orden aleatorio, si no hacemos nada anulará su impresión y forzará el orden deseado.
Para declarar un mapa se hace con el valor del mapa[clave], donde clave será el tipo que queremos que sea nuestra clave (debe ser de un tipo comparable https://go.dev/ref/spec#Comparison_operators ) y valor será el tipo que queremos que se almacene el mapa en cada una de las claves, sea del tipo que sea, desde un int hasta una estructura, u otro mapa, lo que queramos.
Al igual que con los sectores, los mapas son tipos referenciados, lo que significa que el valor cero de un mapa será nulo.
Esto sucede porque debajo hay una tabla hash que almacena las claves y los valores, y son simplemente un sobre, una abstracción, de ellos.
Si lo declaramos como:
var m map[int]int
su valor será nulo.
Si queremos que tenga valor cero, podemos usar la declaración:
m := map[int]int{}
E incluso podemos inicializarlo como los cortes, usando la función make.
m := make(map[string]string)
Al hacer esto, se inicializará un mapa hash con el grupo de memoria adecuado, devolviendo así un mapa que apunta a esa estructura de datos.
La adición de valores a un mapa se realiza mediante llaves [] y llaves, al igual que con matrices o cortes. En este ejemplo, crearemos un mapa con las claves como cadenas y los valores como números enteros, para almacenar nombres y edades.
ages := make(map[string]int) ages["John"] = 33 ages["Charly"] = 27 ages["Jenny"] = 45 ages["Lisa"] = 19
Si queremos agregarle los valores cuando declaramos el mapa, podemos usar la declaración corta y hacerlo todo en el mismo paso:
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
Para leer los valores simplemente debemos indicar la clave a nuestro mapa y nos devolverá ese valor. Por ejemplo, para saber la edad de Lisa, podemos hacer:
fmt.Println(ages["Lisa"]) // 19
Si intentamos acceder a una clave que no existe, el valor obtenido será el valor cero del tipo, en este caso sería "", ya que es una cadena.
Para comprobar si un elemento existe en el mapa, podemos comprobar si el tipo es el predeterminado, pero no es muy confiable, ya que tal vez exista pero su valor sea una cadena vacía o 0 en el caso de int , que coincidiría con su valor cero, por lo que Go nos ayuda con lo siguiente:
val, ok := ages["Randy"]
Si igualamos el mapa a dos valores, el primero será el valor de ese elemento al que se accede mediante la clave, en este caso "Randy" que no existe, y el segundo será un booleano, que indicará si existe o no.
Si no estamos interesados en el valor y simplemente queremos verificar la existencia de una clave, podemos usar _ para ignorar el valor de la siguiente manera:
_, ok := ages["Randy"]
Al igual que con las matrices y los cortes, podemos usar la función len para averiguar cuántos elementos hay en el mapa.
fmt.Println(len(ages)) // 4
Si queremos modificar un valor, es tan sencillo como acceder a dicho valor mediante una clave y hacerla coincidir con otra, y quedará modificado.
Si declaramos un segundo mapa apuntando al primero, si modificamos el valor del segundo, al ser un tipo referenciado, estaremos modificando el valor del primero, porque ambos comparten la misma tabla hash debajo.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} agesNew := ages agesNew["Bryan"] = 77 fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19] fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
Para eliminar elementos de un mapa, Go nos proporciona una función de eliminación con la siguiente firma eliminar(m mapa[Tipo]Tipo1, tipo de clave) que recibe un mapa y la clave a eliminar.
En el caso anterior, si quisiéramos eliminar a "Lisa" lo haríamos:
delete(ages, "Lisa")
Si queremos recorrer el contenido de un mapa, podemos hacerlo usando un for con la variación de rango que ya vimos en el post sobre arrays y cortes.
Como entonces, el primer elemento será el índice, por tanto la clave, y el segundo el valor.
for key, value := range ages { fmt.Printf("%s: %d\n", key, value) } // Output: // Jenny: 45 // Lisa: 19 // John: 33 // Charly: 27
Al igual que con los arrays y los cortes, si solo nos interesa el valor, sin la clave, podemos omitirlo usando _.
for _, value := range ages { fmt.Println(value) } // Output: // 19 // 33 // 27 // 45
Y si lo que nos interesa es simplemente la clave, podemos asignar el rango a una sola variable para obtenerla:
for key := range ages { fmt.Println(key) } // Output: // John // Charly // Jenny // Lisa
Como mencioné en la introducción, en un mapa la información no está ordenada, por lo que al recorrerlo no podemos especificar qué orden sigue, ni Go puede garantizar que el orden entre ejecuciones sea el mismo.
Como vimos con arrays y cortes, en la biblioteca estándar hay un paquete de clasificación que nos ayuda a ordenar elementos: https://pkg.go.dev/sort
Siguiendo nuestro ejemplo con edades y usando sort, podemos ordenar las claves del mapa antes de recorrerlo y así garantizar que se accederá en orden.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} keys := make([]string, 0, len(ages)) for k := range ages { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, ages[k]) } // Output: // Charly 27 // Jenny 45 // John 33 // Lisa 19
Declaramos nuestro mapa de edades con la declaración corta como vimos antes.
Creamos un string slices para almacenar las claves y usamos el método make con longitud 0, ya que no tenemos claves en este momento, pero sí reservamos la capacidad que tendrá usando el método len para la longitud de nuestro mapa. &&&]
Recorremos el mapa de edades para conservar sus claves y agregarlas al segmento creado.
Ordenamos las claves alfabéticamente con la función sort.Strings.
Recorremos el trozo de llaves, ya ordenado, y accedemos al mapa con la llave en cuestión.
De esta manera accederemos al mapa de forma ordenada y podremos hacer la lógica que nuestro programa necesita.
El caso problemático es cuando quieres actualizar el valor de un mapa al mismo tiempo, ya sea agregando o quitando elementos del mismo, y al mismo tiempo lo estás leyendo desde otro lado, por ejemplo.
Para solucionar esta situación existen varias soluciones posibles, las cuales no entraré en mucho detalle, simplemente mencionaré y dejaré a tu elección profundizar en ellas.
Un posible uso es el tipo RWMutex que nos permite bloquear y desbloquear lecturas y escrituras en un tipo. Entonces, si tenemos un tipo que contiene un sync.RWMutex y un mapa, podemos controlar cuándo se puede acceder a él.
Otro tipo interesante a investigar dentro del mismo paquete de sincronización es Map, que ya nos ofrece una serie de funciones que nos ayudarán a trabajar con nuestro mapa, que al final no podremos trabajar de forma nativa, como ocurre con la solución anterior.
Dependiendo del caso de uso que estemos implementando nos será más útil uno u otro, y no hay uno mejor que otro, siempre dependerá de lo que necesitemos.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3