Conceptos básicos de los diccionarios en Go

GolangGolangBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Bienvenidos, Gophers, a este nuevo capítulo.

En este capítulo, aprenderemos sobre el uso básico de los mapas y analizaremos la presencia de nil en Go.

Después de completar este capítulo, podrás aplicar los mapas a tus tareas de desarrollo diario.

Puntos de conocimiento:

  • Declarar un mapa
  • Inicializar un mapa
  • Agregar, eliminar, actualizar y buscar elementos de un mapa
  • Verificar si un elemento de un mapa existe
  • Iterar sobre un mapa

Introducción a los diccionarios

Entonces, ¿qué es un diccionario?

En ciencias de la computación, un diccionario es una estructura de datos especial que consiste en pares clave-valor.

Par clave-valor: Un par de elementos, donde uno se llama clave y el otro se llama valor.

Entonces, ¿por qué necesitamos usar un diccionario?

Debido a que los diccionarios son muy eficientes en operaciones como agregar, eliminar, actualizar y buscar elementos, estoy seguro de que encontrarás esta estructura de datos muy útil.

Descripción de la imagen

Declaración de mapas

Para una nueva estructura de datos, el primer paso es aprender cómo declararla.

Para un map, la forma de definirlo es:

var variableName map[keyType]valueType

Por ejemplo:

var a map[string]int

Sin embargo, dado que un map es un tipo de referencia, si lo declaramos como en el código anterior, encontraremos un problema.

Creemos un archivo map.go en el directorio ~/project:

touch ~/project/map.go

Escribamos el siguiente código en map.go:

package main

func main() {
    // Declare a map named m with key type as string and value type as int
    var m map[string]int
    // Add a data entry "labex"->1 to it
    m["labex"] = 1 // Program crashes
}

Ejecutemos el programa:

go run map.go

Descubrimos que el programa se bloqueó y arrojó un error:

panic: assignment to entry in nil map

Esto significa que asignar un elemento a un nil map es un comportamiento erróneo.

Esto se debe a que, para estructuras de datos como slices, mapas, canales y punteros, deben inicializarse antes de su uso.

Y nil es el valor predeterminado inicial de un mapa, lo que significa que no se asigna memoria para la variable durante la definición.

El valor inicial nil

En la sección anterior, mencionamos que es un comportamiento erróneo asignar un elemento a un mapa nil.

Aprovechemos esta oportunidad para explorar el verdadero significado de nil en Go.

La esencia de nil es un identificador pre-declarado.

Para los tipos de datos básicos, sus valores iniciales son diferentes:

  • Valores booleanos
  • Valores numéricos
  • Cadenas de texto

Sin embargo, para slices, diccionarios, punteros, canales y funciones, su valor inicial es nil. No es el valor predeterminado inicial con el que estamos familiarizados.

Esto se refleja en los objetos instanciados que se les asigna nil. Aunque se pueden imprimir, no se pueden usar.

Además, hay algunos puntos a tener en cuenta sobre nil.

Incomparabilidad de diferentes tipos de nil

package main

import "fmt"

func main() {
    var m map[int]string
    var p *int
    fmt.Printf("%v", m == p)
}

La salida del programa es la siguiente:

invalid operation: m == p (mismatched types map[int]string and *int)

Es decir, no podemos comparar el nil de un puntero de tipo int con el nil de un mapa.

No son comparables.

nil no es una palabra clave

package main

import "fmt"

func main() {
    var nilValue = "= =$"
    fmt.Println(nilValue)
}

La salida del programa es la siguiente:

= =$

Podemos definir una variable llamada nil y se puede compilar sin errores. Sin embargo, les recomendamos encarecidamente que no hagan esto en el desarrollo real.

Incomparabilidad de nil

package main

import "fmt"

func main() {
    fmt.Println(nil == nil)
}

La salida del programa es la siguiente:

invalid operation: nil == nil (operator == not defined on nil)

Declaración con la palabra clave make

Después de entender el significado de nil, resolvamos el problema de la asignación de memoria para los diccionarios.

Aquí utilizamos la palabra clave make para asignar memoria.

La función make genera un valor de un tipo especificado que ha sido inicializado como su valor de retorno.

package main

import "fmt"

func main() {
    var m map[string]int     // Declare a dictionary
    m = make(map[string]int) // Allocate memory to the dictionary
    m["labex"] = 1       // Add data to the dictionary
    fmt.Println(m)
}

La salida del programa es la siguiente:

map[labex:1]

El código anterior demuestra cómo asignar memoria a un diccionario que ha sido declarado utilizando la palabra clave make.

También podemos simplificar este proceso:

Escribe el siguiente código en map.go:

package main

import "fmt"

func main() {
    m := make(map[string]int) // Declare and initialize a dictionary
    m["labex"] = 666      // Add data to the dictionary
    fmt.Println(m)
}

La salida del programa es la siguiente:

map[labex:666]

El código anterior muestra cómo declarar un diccionario utilizando la palabra clave make.

Inicialización manual de un mapa vacío

En la sección anterior, demostramos cómo usar la palabra clave make para inicializar un mapa. Ahora, exploremos otro método: usar la sintaxis literal para inicializar manualmente un mapa vacío.

Este método es conciso y te permite crear un mapa vacío listo para usar sin asignar memoria explícitamente.

Escribe el siguiente código en map.go:

package main

import "fmt"

func main() {
    // Using literal syntax to initialize an empty map
    m := map[string]int{}

    // Adding elements to the map
    m["labex"] = 777
    m["labs"] = 11

    fmt.Println(m) // Output the map with added elements
}

La sintaxis map[keyType]valueType{} crea un mapa vacío listo para usar. Una vez inicializado, puedes agregar elementos al mapa usando la sintaxis map[key] = value.

Cuando ejecutes el código anterior, el programa mostrará lo siguiente:

map[labex:777 labs:11]

Beneficios de la inicialización manual:

  • Proporciona una forma rápida de crear un mapa vacío sin usar la palabra clave make.
  • Es útil cuando necesitas comenzar con un mapa vacío y llenarlo de forma dinámica.

Inicialización real del diccionario

Dado que podemos inicializar un diccionario vacío, también podemos darle algunos valores iniciales.

Escribe el siguiente código en map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
    }
    m["labby"] = 666
    fmt.Println(m)
}

La salida del programa es la siguiente:

map[labby:666 labs:11 labex:777]

Ten en cuenta que al inicializar un diccionario en Go, se debe agregar una coma después de cada elemento, incluyendo el último.

Agregar y actualizar elementos de un diccionario

Agregar elementos a un diccionario es muy sencillo, como se demostró en el ejemplo de código anterior.

La sintaxis es:

dictionaryInstance[keyToAdd] = valueToAdd

Por ejemplo:

m := map[string]int{}
m["labby"] = 666

Nota: En Go, cada key (clave) en un map (mapa) debe ser única.

Escribe el siguiente código en map.go:

package main

import "fmt"

func main() {
    m := map[string]int{}
    m["labby"] = 666
    m["labby"] = 777
    fmt.Println(m)
}

La salida del programa es la siguiente:

map[labby:777]

Observamos que el valor de la salida se cambió a 777. Es decir, si escribimos diferentes valores para la misma key (clave), el value (valor) correspondiente se actualizará al nuevo valor.

Así es como actualizamos o modificamos un diccionario.

Eliminación de elementos de un diccionario

¿Cómo podemos eliminar un elemento de un diccionario?

Necesitamos utilizar la función incorporada delete.

Escribe el siguiente código en map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 777,
        "labs": 11,
        "labby": 666,
    }
    fmt.Println(m)
    delete(m, "labs")
    fmt.Println(m)
}

La salida del programa es la siguiente:

map[labby:666 labs:11 labex:777]
map[labby:666 labex:777]

La función delete toma dos argumentos. El primer argumento es el diccionario sobre el que se realizará la operación, y el segundo argumento es la clave que se desea eliminar.

Por supuesto, la función delete tiene otros usos, pero en este laboratorio (lab) solo discutiremos su uso en diccionarios.

Búsqueda de elementos en un diccionario

¿Qué sucede cuando buscamos un elemento en un diccionario que no existe?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
}

La salida del programa es la siguiente:

The value of labs is: 0

Observamos que si el elemento no existe en el diccionario, consultarlo devolverá el valor predeterminado del tipo de valor.

¿Qué pasa con la clave en el diccionario cuyo valor es 0?

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    fmt.Print("The value of labs is: ")
    fmt.Println(m["labs"])
    fmt.Print("The value of labex is: ")
    fmt.Println(m["labex"])
}

La salida del programa es la siguiente:

The value of labs is: 0
The value of labex is: 0

Observamos que para un diccionario, la representación de un valor que no existe y un valor que existe pero tiene un valor predeterminado es la misma.

Esto nos causa mucha confusión. ¿Cómo lo resolvemos?

Resulta que los desarrolladores de Go ya habían pensado en este problema. Cuando consultamos un elemento en un diccionario, habrá uno o dos valores de retorno de variables.

Es decir:

labs, ok := m["labs"]

Modifica map.go:

package main

import "fmt"

func main() {
    m := map[string]int{
        "labex": 0,
    }
    labs, ok := m["labs"]
    fmt.Print("The value of labs is: ")
    fmt.Print(labs)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok)
    labex, ok2 := m["labex"]
    fmt.Print("The value of labex is: ")
    fmt.Print(labex)
    fmt.Print(" Does it exist? ")
    fmt.Println(ok2)
}

La salida del programa es la siguiente:

The value of labs is: 0 Does it exist? false
The value of labex is: 0 Does it exist? true

Ahora, podemos usar el segundo valor de retorno de la consulta para determinar si el resultado devuelto es un valor predeterminado inicial existente o un valor predeterminado inicial inexistente.

Iteración sobre diccionarios

En ciertos escenarios, necesitamos iterar sobre un diccionario completo, consultar cada par clave-valor y procesarlos. ¿Cómo podemos lograr esto?

package main

import (
    "fmt"
)

func main() {
    m := map[string]int{
        "labex": 777,
        "labs":   11,
        "labby":     666,
    }
    for key, value := range m {
        fmt.Println(key, value)
    }
}

La salida del programa es la siguiente:

labby 666
labs 11
labex 777

A partir de la salida, podemos ver que la forma de iterar sobre un diccionario es muy similar a la de iterar sobre slices (rebanadas) o arrays (arreglos).

Resumen

En este laboratorio (lab), aprendimos sobre el uso fundamental de los diccionarios, incluyendo:

  • La declaración de diccionarios y cómo resolver el problema de declaración
  • Las características del valor inicial nil
  • Los métodos de inicialización de diccionarios
  • La adición, eliminación, actualización y búsqueda de elementos en diccionarios
  • La detección de la existencia de elementos en diccionarios
  • La iteración sobre diccionarios