Ordenación de Diccionarios en Go

GolangBeginner
Practicar Ahora

Introducción

A diferencia de otros lenguajes, en Go, los diccionarios (maps) son colecciones desordenadas. En esta práctica de laboratorio, aprenderemos sobre la ordenación de diccionarios y cómo utilizarlos de manera más flexible.

Conceptos Clave:

  • Ordenación de diccionarios
  • Intercambio de claves y valores en un diccionario
  • Slices de diccionarios
  • Diccionarios con slices como valores
  • Características de los diccionarios como tipos de referencia

Ordenación de Diccionarios

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

touch ~/project/map.go

Escribe el siguiente código en el archivo map.go:

package main

import (
 "fmt"
)

func main() {
 // Declare and initialize a map with string keys and integer values
 // The map stores student names and their scores
 m := map[string]int{
  "Alice":   99, // Each key-value pair represents a student and their score
  "Bob":     38,
  "Charlie": 84,
 }

 // Iterate through the map using a for-range loop
 // 'key' represents student names, 'value' represents scores
 for key, value := range m {
  fmt.Println(key, value)
 }

 fmt.Println("\nInsert Data")

 // Demonstrate how to add a new key-value pair to the map
 // The syntax is: map[key] = value
 m["David"] = 25

 // Iterate again to show the updated map contents
 // Note that the order may be different in each execution
 for key, value := range m {
  fmt.Println(key, value)
 }
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa podría verse así:

Charlie 84
Bob 38
Alice 99

Insert Data
David 25
Charlie 84
Bob 38
Alice 99

La salida puede variar ya que el orden de los datos insertados no es fijo. Esta es una característica fundamental de los maps en Go: no garantizan ningún orden particular de los elementos cuando se itera a través de ellos.

Puedes intentar ejecutar el programa varias veces y observar que el orden de los datos insertados puede cambiar. Esto ilustra que no puedes confiar en el orden de los elementos en un map.

Sin embargo, a veces necesitamos ordenar el diccionario después de insertar los datos. ¿Cómo podemos lograrlo?

Dado que un map en sí mismo no se puede ordenar, podemos convertir el map en un slice y luego ordenar dicho slice.

Ordenar por Clave

Primero, aprendamos cómo ordenar el diccionario por su clave.

Estos son los pasos:

  • Convertir las claves del diccionario en un slice. Un slice es una estructura ordenada y dinámica que podemos clasificar.
  • Ordenar el slice utilizando el paquete nativo sort de Go.
  • Recuperar los valores correspondientes utilizando el método de búsqueda del diccionario. Como conocemos el orden de las claves en nuestro slice, podemos buscar los valores en el map y aparecerán en ese orden de claves ordenadas.

Escribe el siguiente código en el archivo map.go:

package main

import (
 "fmt"
 "sort"
)

func main() {
 // Initialize the dictionary
 m1 := map[int]string{
  3: "Bob",
  1: "Alice",
  2: "Charlie",
 }
 keys := make([]int, 0, len(m1)) // Initialize the slice with capacity. This is a performance optimization -  the slice will allocate memory equal to the size of the map initially, avoiding re-allocations.
 for key := range m1 {
  // Convert the key to a slice
  keys = append(keys, key)
 }
 // Sort the key slice using the sort package. The `sort.Ints()` function specifically sorts a slice of integers.
 sort.Ints(keys)
 for _, key := range keys {
  // The output is in order now
  fmt.Println(key, m1[key])
 }
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

1 Alice
2 Charlie
3 Bob

Mediante este enfoque, hemos logrado ordenar el diccionario basándonos en la clave. Las claves se extraen a un slice, se ordenan y luego se usan para buscar e imprimir los valores correspondientes del map.

Intercambio de Claves y Valores en un Diccionario

Antes de explicar cómo ordenar por valor, aprendamos a intercambiar las claves y los valores en un diccionario.

Intercambiar claves y valores significa permutar las posiciones de ambos en un diccionario, como se muestra en la siguiente imagen:

Intercambio de clave y valor en diccionario

El código de implementación es sencillo. Escribe el siguiente código en el archivo map.go:

package main

import "fmt"

func main() {
 m := map[string]int{
  "Alice":   99,
  "Bob":     38,
  "Charlie": 84,
 }
 m2 := map[int]string{}
 for key, value := range m {
  m2[value] = key
 }
 fmt.Println(m2)
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

map[38:Bob 84:Charlie 99:Alice]

La esencia de este código es extraer las claves y valores del diccionario original y luego reinsertarlos en un nuevo diccionario pero con los roles intercambiados. Es simple y directo. Ten en cuenta que, debido a que el map no tiene orden, el orden de salida del map intercambiado puede variar.

Ordenar por Valor

Combinando la lógica de ordenar el diccionario por clave y el intercambio de claves y valores, podemos ordenar el diccionario por su valor.

Así es como funciona: intercambiamos las claves y los valores, y luego tratamos las claves intercambiadas (que eran los valores originales) como la base para nuestra ordenación. Después, usamos esas "claves" ordenadas (valores originales) para buscar las claves originales correspondientes en el map intercambiado.

Escribe el siguiente código en el archivo map.go:

package main

import (
 "fmt"
 "sort"
)

func main() {
 // Initialize the dictionary
 m1 := map[string]int{
  "Alice":   99,
  "Bob":     38,
  "Charlie": 84,
 }

 // Initialize the reversed dictionary
 m2 := map[int]string{}
 for key, value := range m1 {
  // Generate the reversed dictionary m2 by swapping the key-value pairs
  m2[value] = key
 }

 values := make([]int, 0) // Initialize the sorting slice
 for _, value := range m1 {
  // Convert the values of the original dictionary to a slice
  values = append(values, value)
 }
 // Sort the value slice using the sort package
 sort.Ints(values)

 for _, value := range values {
  // The output is in order now
  fmt.Println(m2[value], value)
 }
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

Bob 38
Charlie 84
Alice 99

Ahora hemos ordenado el diccionario basándonos en sus valores. Convertimos los valores a un slice, ordenamos el slice y luego usamos los valores ordenados para recuperar e imprimir las claves originales correspondientes desde el map intercambiado.

Ordenación mediante sort.Slice

Si la versión de Go es posterior a la 1.7, podemos usar la función sort.Slice para ordenar rápidamente un map por clave o valor. sort.Slice permite especificar una función de comparación personalizada.

Aquí tienes un ejemplo:

package main

import (
 "fmt"
 "sort"
)

func main() {
 m1 := map[int]int{
  21: 99,
  12: 98,
  35: 17,
  24: 36,
 }

 type kv struct {
  Key   int
  Value int
 }

 var s1 []kv

 for k, v := range m1 {
  s1 = append(s1, kv{k, v})
 }

 sort.Slice(s1, func(i, j int) bool {
  return s1[i].Key < s1[j].Key
 })

 fmt.Println("Sorted in ascending order by key:")
 for _, pair := range s1 {
  fmt.Printf("%d, %d\n", pair.Key, pair.Value)
 }
}

Ejecuta el programa:

go run ~/project/map.go

La salida es la siguiente:

Sorted in ascending order by key:
12, 98
21, 99
24, 36
35, 17

En este programa, utilizamos una estructura kv para almacenar los pares clave-valor del map. Luego, ordenamos el slice de estructuras usando la función sort.Slice() y una función de comparación anónima. Esta función de comparación (func(i, j int) bool) determina el orden de clasificación basándose en el campo Key de nuestra estructura.

También podemos usar sort.Slice para ordenar el map en orden descendente por clave o en orden ascendente por valor modificando esta función de comparación. Esto proporciona una gran flexibilidad en la forma en que queremos organizar los datos de nuestro map.

Pequeño Desafío

Crea un archivo map2.go y modifica el código de la sección anterior para ordenar el map en orden descendente basándote en el valor.

Salida Esperada:

Ejecuta el programa:

go run ~/project/map2.go
Sorted in descending order by value:
21, 99
12, 98
24, 36
35, 17

Requisitos:

  • El archivo map2.go debe ubicarse en el directorio ~/project.
  • Debes utilizar la función sort.Slice. Necesitarás modificar la función de comparación utilizada en sort.Slice del ejemplo anterior.

Slices de Diccionarios

Al igual que podemos usar arrays o slices para almacenar datos relacionados, también podemos usar slices donde los elementos son diccionarios. Esto nos permite manejar colecciones de datos tipo map, lo cual es muy útil para trabajar con información estructurada.

Escribe el siguiente código en el archivo map.go:

package main

import "fmt"

func main() {
 // Declare a slice of maps and initialize it using make
 var mapSlice = make([]map[string]string, 3) // Creates a slice with a capacity of 3, where each element can be a `map[string]string`.
 for index, value := range mapSlice {
  fmt.Printf("index:%d value:%v\n", index, value)
 }
 fmt.Println("Initialization")
 // Assign values to the first element of the slice
 mapSlice[0] = make(map[string]string, 10) // Creates a map at the first index.
 mapSlice[0]["name"] = "labex"
 mapSlice[0]["password"] = "123456"
 mapSlice[0]["address"] = "Paris"
 for index, value := range mapSlice {
  fmt.Printf("index:%d value:%v\n", index, value)
 }
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

index:0 value:map[]
index:1 value:map[]
index:2 value:map[]
Initialization
index:0 value:map[address:Paris name:labex password:123456]
index:1 value:map[]
index:2 value:map[]

El código demuestra la inicialización de un slice de maps. Inicialmente, cada elemento del slice es un map vacío. Luego, creamos y asignamos un map al primer elemento, poblándolo con datos. Esto muestra cómo gestionar una lista de diccionarios.

Diccionarios con Slices como Valores

También podemos usar diccionarios con slices como valores para almacenar más datos en un solo diccionario. Esto permite asociar múltiples valores a una única clave en un map, creando efectivamente una relación de "uno a muchos".

Escribe el siguiente código en el archivo map.go:

package main

import "fmt"

func main() {
 var sliceMap = make(map[string][]string, 3) // Declares a map where keys are strings and values are slices of strings. The 3 is a capacity hint.
 key := "labex"
 value, ok := sliceMap[key]  // Checks if the key exists
 if !ok {
  value = make([]string, 0, 2) // If it doesn't exist, initialize a new slice with capacity.
 }
 value = append(value, "Paris", "Shanghai") // Appends values to the slice.
 sliceMap[key] = value // Sets the slice as the value for our key in the map.
 fmt.Println(sliceMap)
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

map[labex:[Paris Shanghai]]

El código primero declara un tipo de map donde los valores son slices. Verifica si la clave existe antes de agregar nuevos elementos al slice asociado, demostrando un patrón común para manejar maps de slices.

Características de los Diccionarios como Tipos de Referencia

Los arrays son tipos de valor, por lo que asignarlos y pasarlos a funciones crea una copia. Los cambios en la copia no afectan al array original. Los maps, sin embargo, son tipos de referencia. Esto significa que asignar y pasar un map a una función no crea una copia completa. En su lugar, el map se pasa por referencia.

Esto es importante, ya que los cambios realizados dentro de la función afectarán a los datos del map original.

Escribe el siguiente código en el archivo map.go:

package main

import "fmt"

func modifyMap(x map[string]int) {
 x["Bob"] = 100 // Modifies the map that was passed as an argument.
}

func main() {
 a := map[string]int{
  "Alice":   99,
  "Bob":     38,
  "Charlie": 84,
 }
 // Because the map is passed by reference, the modification in modifyMap changes the original dictionary
 modifyMap(a)
 fmt.Println(a) // map[Alice:99 Bob:100 Charlie:84]
}

Ejecuta el programa:

go run ~/project/map.go

La salida del programa es la siguiente:

map[Alice:99 Bob:100 Charlie:84]

En este ejemplo, demostramos la característica de transferencia por referencia de un diccionario. La función modifyMap cambia el map original porque a es una referencia a los mismos datos subyacentes del map. Es crucial entender este comportamiento al pasar maps a funciones.

Resumen

En esta práctica de laboratorio, hemos aprendido sobre el uso avanzado de los maps en Go, incluyendo:

  • Ordenación de maps por clave o valor, lo que implica convertir el map en un slice y luego ordenar dicho slice.
  • Intercambio de claves y valores en un map, lo que crea un nuevo map con los roles invertidos.
  • Uso de slices de maps, lo que permite crear colecciones de datos de maps relacionados.
  • Uso de maps con slices como valores, lo que permite asociar múltiples valores con una sola clave.
  • Características de los maps como tipos de referencia, donde los cambios en un map pasado se reflejan en el map original.

Comprender estos conceptos te permitirá utilizar los maps de Go de manera más efectiva en aplicaciones del mundo real.

✨ Revisar Solución y Practicar