Ordenar 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

A diferencia de otros lenguajes, en Go, los diccionarios (maps) son colecciones desordenadas. En este laboratorio (lab), aprenderemos sobre cómo ordenar diccionarios y cómo usarlos de manera más flexible.

Conceptos clave:

  • Ordenar diccionarios
  • Intercambiar claves y valores en un diccionario
  • Slices de diccionarios
  • Diccionarios con slices como valores
  • Características de tipo referencia de los diccionarios

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("Golang")) -.-> go/BasicsGroup(["Basics"]) go(("Golang")) -.-> go/DataTypesandStructuresGroup(["Data Types and Structures"]) go(("Golang")) -.-> go/FunctionsandControlFlowGroup(["Functions and Control Flow"]) go(("Golang")) -.-> go/AdvancedTopicsGroup(["Advanced Topics"]) go/BasicsGroup -.-> go/values("Values") go/DataTypesandStructuresGroup -.-> go/slices("Slices") go/DataTypesandStructuresGroup -.-> go/maps("Maps") go/DataTypesandStructuresGroup -.-> go/structs("Structs") go/FunctionsandControlFlowGroup -.-> go/for("For") go/FunctionsandControlFlowGroup -.-> go/functions("Functions") go/AdvancedTopicsGroup -.-> go/sorting("Sorting") subgraph Lab Skills go/values -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/slices -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/maps -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/structs -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/for -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/functions -.-> lab-149095{{"Ordenar Diccionarios en Go"}} go/sorting -.-> lab-149095{{"Ordenar Diccionarios en Go"}} end

Ordenar 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 puede ser similar a esta:

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 está fijo. Esta es una característica fundamental de los mapas (maps) en Go: no garantizan ningún orden particular de los elementos cuando se itera sobre ellos.

Puedes probar a ejecutar el programa varias veces y observar que el orden de los datos insertados puede variar. Esto demuestra que no se puede confiar en el orden de los elementos en un mapa.

Pero a veces necesitamos ordenar el diccionario después de insertar datos. ¿Cómo podemos lograr eso?

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

Ordenar por Clave

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

Estos son los pasos:

  • Convertir las claves del diccionario en un slice. Un slice es una matriz dinámica y ordenada que podemos ordenar.
  • Ordenar el slice utilizando el paquete sort incorporado en Go.
  • Recuperar los valores correspondientes utilizando el método de búsqueda del diccionario. Dado que conocemos el orden de las claves en nuestro slice, podemos buscar los valores en el mapa 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

A través de este enfoque, hemos logrado ordenar el diccionario basado en la clave. Las claves se extraen en un slice, se ordenan y luego se utilizan para buscar e imprimir los valores correspondientes del mapa.

Intercambiar Claves y Valores en un Diccionario

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

Intercambiar claves y valores significa intercambiar las posiciones de las claves y los valores en un diccionario, como se muestra en la siguiente figura:

Dictionary key value swap

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 los valores del diccionario original y luego reinsertarlos en el diccionario, pero con roles intercambiados. Es simple y directo. Ten en cuenta que, debido a que el mapa (map) es desordenado, el orden de salida del mapa intercambiado puede variar.

Ordenar por Valor

Combinando la lógica de ordenar el diccionario por clave y de intercambiar claves y valores en un diccionario, podemos ordenar el diccionario por valor.

Así es cómo funciona: intercambiamos las claves y los valores, y luego tomamos las claves intercambiadas (valores originales) como base para nuestro ordenamiento. Luego, usamos las "claves" ordenadas (valores originales) para buscar las claves originales correspondientes en el mapa 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 basado en sus valores. Convertimos los valores en un slice, ordenamos el slice y luego usamos los valores ordenados para recuperar e imprimir las claves originales correspondientes del mapa intercambiado.

Ordenado por sort.Slice

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

A continuación, 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 (struct) kv para almacenar los pares clave-valor del mapa. Luego, ordenamos el slice de estructuras utilizando 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 basado en el campo Key de nuestra estructura.

También podemos utilizar sort.Slice para ordenar el mapa en orden descendente por clave o en orden ascendente por valor modificando esta función de comparación. Esto brinda una gran flexibilidad en cómo queremos ordenar nuestros datos de mapa.

Pequeña Prueba

Crea un archivo map2.go y modifica el código de la sección anterior para ordenar el mapa (map) en orden descendente basado 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 colocarse 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.
✨ Revisar Solución y Practicar

Slices de Diccionarios

Así como podemos usar arrays o slices para almacenar datos relacionados, también podemos usar slices donde los elementos son diccionarios. Esto nos permite almacenar colecciones de datos de mapa (map), lo cual puede ser ú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 mapas. Inicialmente, cada elemento en el slice es un mapa vacío. Luego, creamos y asignamos un mapa al primer elemento, llenándolo con datos. Esto muestra cómo manejar una lista de mapas.

Diccionarios con Slices como Valores

También podemos utilizar diccionarios con slices como valores para almacenar más datos en un diccionario. Esto nos permite asociar múltiples valores con una sola clave en un mapa (map), creando efectivamente una relación "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 mapa donde los valores son slices. Comprueba si la clave existe antes de agregar nuevos elementos al slice asociado, lo que demuestra un patrón común para manejar mapas de slices.

Características de Tipo Referencia de los Diccionarios

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. Sin embargo, los mapas (maps) son tipos de referencia. Esto significa que asignar y pasar un mapa a una función no crea una copia completa. En lugar de eso, el mapa se pasa por referencia.

Esto es importante, ya que los cambios realizados dentro de la función afectarán los datos del mapa 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 el ejemplo, demostramos la característica de transferencia por referencia de un diccionario. La función modifyMap cambia el mapa original porque a es una referencia a los mismos datos subyacentes del mapa. Este comportamiento es crucial para entender cuando se pasan mapas a funciones.

Resumen

En este laboratorio, hemos aprendido sobre el uso avanzado de los mapas (maps) en Go, incluyendo:

  • Ordenar mapas por clave o valor, lo que implica convertir el mapa en un slice y luego ordenar el slice.
  • Intercambiar claves y valores en un mapa, lo que crea un nuevo mapa con roles invertidos.
  • Utilizar slices de mapas, lo que permite crear colecciones de datos de mapas relacionados.
  • Utilizar mapas con slices como valores, lo que permite asociar múltiples valores con una sola clave.
  • Características de tipo referencia de los mapas, donde los cambios en un mapa pasado se reflejan en el mapa original.

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