«¿Dónde está mi Set?» — el grito del alma del desarrollador Go
\n\nSi vienes a Go desde Python, JavaScript o Java, tarde o temprano te preguntarás: «¿Dónde diablos está mi querido Set?» — y es normal. En Stack Overflow, el tema «Golang why don't we have a set datastructure» ha acumulado miles de visitas y decenas de acalorados debates. Los desarrolladores están acostumbrados a un conjunto conveniente con elementos únicos, pero Go solo ofrece arrays, slices y maps. En este artículo analizaremos la filosofía de los creadores de Go, aprenderemos a emular Set e incluso escribiremos nuestra propia implementación minimalista.
¿Por qué los creadores de Go no agregaron Set a la biblioteca estándar?
\n\nLa respuesta está en el minimalismo y pragmatismo del lenguaje. El equipo de Go (Rob Pike, Ken Thompson, Robert Griesemer) se adhirió al principio: «Si algo se puede hacer con una sola construcción del lenguaje, no multipliques las entidades». En Go, map no es solo un diccionario, sino un contenedor universal. Ya soporta la unicidad de las claves, que es la propiedad principal de un conjunto.
Filosofía «menos magia, más claridad»
\n\nA diferencia de Python, donde set es un tipo separado con sus propios métodos (.add(), .union()), en Go decidieron no complicar el lenguaje. ¿Para qué escribir una estructura separada si map[T]struct{} hace lo mismo? Esto ahorra espacio en la biblioteca estándar y reduce la barrera de entrada para los principiantes. Lo principal es aprender a usar correctamente map como set.
Cómo emular Set en Go: tres enfoques prácticos
\n\nPasemos a la práctica. Así es como se puede implementar un conjunto usando solo las herramientas estándar de Go.
\n\n1. El más popular: map[type]struct{}
\n\nEste es el enfoque estándar en la comunidad Go. ¿Por qué struct{}? Porque la estructura vacía no ocupa memoria (0 bytes) — ideal como marca de «existencia» del elemento.
package main\n\nimport "fmt"\n\nfunc main() {\n // Creamos un conjunto de números enteros\n set := make(map[int]struct{})\n\n // Agregamos elementos\n set[1] = struct{}{}\n set[2] = struct{}{}\n set[3] = struct{}{}\n set[1] = struct{}{} // El duplicado se ignora\n\n // Verificación de existencia\n if _, exists := set[2]; exists {\n fmt.Println("2 está en el conjunto")\n }\n\n // Eliminación\n delete(set, 3)\n\n // Iteración\n for key := range set {\n fmt.Println(key)\n }\n}\n\nVentajas: asignación de memoria cero para los valores, simplicidad. Desventajas: no hay métodos .Add() o .Contains() — todo se hace a través de la sintaxis de map.
2. Tipo propio con métodos
\n\nSi necesita una API limpia, envuelva el map en una estructura:
\n\npackage main\n\nimport "fmt"\n\n// Set — conjunto genérico (Go 1.18+)\ntype Set[T comparable] struct {\n items map[T]struct{}\n}\n\n// NewSet crea un nuevo Set\nfunc NewSet[T comparable]() *Set[T] {\n return &Set[T]{items: make(map[T]struct{})}\n}\n\n// Add agrega un elemento\nfunc (s *Set[T]) Add(item T) {\n s.items[item] = struct{}{}\n}\n\n// Contains verifica la presencia\nfunc (s *Set[T]) Contains(item T) bool {\n _, ok := s.items[item]\n return ok\n}\n\n// Size devuelve el tamaño\nfunc (s *Set[T]) Size() int {\n return len(s.items)\n}\n\nfunc main() {\n intSet := NewSet[int]()\n intSet.Add(10)\n intSet.Add(20)\n fmt.Println(intSet.Contains(10)) // true\n fmt.Println(intSet.Size()) // 2\n}\n\nEsto ya se parece a una set datastructure en Go completa. Los genéricos (generics) aparecieron en la versión 1.18, por lo que el código funciona en proyectos modernos.
\n\n3. Uso de bool en lugar de struct{}
\n\nAlgunos usan map[T]bool, pero esto gasta 1 byte por valor. En la mayoría de los casos la diferencia es imperceptible, pero si está escribiendo un servicio de alta carga, es mejor ceñirse a struct{}.