Implementing Sets in Golang
A set is a collection of distinct elements that allows you to easily check for membership, add elements, and remove them without worrying about duplicates. While Go doesn’t have a built-in set type like Python or other languages, we can implement one using Go’s map type.
What is a set?
A set is a data structure that:
- Stores unique elements.
- Allows quick membership checks (i.e., whether an element exists in the set).
- Typically supports operations like union, intersection, difference, and adding/removing elements.
Implementing a set in Go
In Go, the closest thing to a set is a map with keys of the type you want in your set, and the values are just placeholders (often struct{} since it takes no memory).
Here’s how you can implement a simple set:
package main
import "fmt"
// Set is a collection of unique elements
type Set struct {
elements map[string]struct{}
}
// NewSet creates a new set
func NewSet() *Set {
return &Set{
elements: make(map[string]struct{}),
}
}
// Add inserts an element into the set
func (s *Set) Add(value string) {
s.elements[value] = struct{}{}
}
// Remove deletes an element from the set
func (s *Set) Remove(value string) {
delete(s.elements, value)
}
// Contains checks if an element is in the set
func (s *Set) Contains(value string) bool {
_, found := s.elements[value]
return found
}
// Size returns the number of elements in the set
func (s *Set) Size() int {
return len(s.elements)
}
// List returns all elements in the set as a slice
func (s *Set) List() []string {
keys := make([]string, 0, len(s.elements))
for key := range s.elements {
keys = append(keys, key)
}
return keys
}
// Example usage of the Set
func main() {
set := NewSet()
// Add elements to the set
set.Add("apple")
set.Add("banana")
set.Add("orange")
// Check if elements exist in the set
fmt.Println("Contains 'apple':", set.Contains("apple")) // true
fmt.Println("Contains 'grape':", set.Contains("grape")) // false
// List elements
fmt.Println("Set elements:", set.List()) // ["apple", "banana", "orange"]
// Remove an element
set.Remove("banana")
fmt.Println("Set after removal:", set.List()) // ["apple", "orange"]
// Get the size of the set
fmt.Println("Set size:", set.Size()) // 2
}
Let’s walk through it:
- Set Struct: The set is implemented as a struct containing a
map[string]struct{}
. The keys of the map represent the elements of the set, and the value is an empty struct(struct{})
because it takes up no memory and we don’t need to store additional data. - Add Method: The Add method adds an element to the set by setting a key in the map. Since maps don’t allow duplicate keys, this ensures that the set only contains unique elements.
- Remove Method: The Remove method deletes an element from the set by removing the key from the map.
- Contains Method: The Contains method checks whether an element is in the set by looking up the key in the map.
- Size and List Methods: The Size method returns the number of unique elements, and the List method returns all the elements in the set as a slice.
Common Set Operations
Union
The union of two sets is a set containing all elements from both sets.
func (s *Set) Union(other *Set) *Set {
result := NewSet()
for key := range s.elements {
result.Add(key)
}
for key := range other.elements {
result.Add(key)
}
return result
}
// Example usage of Union
set1 := NewSet()
set1.Add("apple")
set1.Add("banana")
set2 := NewSet()
set2.Add("banana")
set2.Add("orange")
unionSet := set1.Union(set2)
fmt.Println("Union:", unionSet.List()) // ["apple", "banana", "orange"]
Intersection
The intersection of two sets is a set containing only the elements present in both sets.
func (s *Set) Intersection(other *Set) *Set {
result := NewSet()
for key := range s.elements {
if other.Contains(key) {
result.Add(key)
}
}
return result
}
// Example usage of Intersection
intersectionSet := set1.Intersection(set2)
fmt.Println("Intersection:", intersectionSet.List()) // ["banana"]
Difference
The difference of two sets is a set containing elements that are in the first set but not in the second.
func (s *Set) Difference(other *Set) *Set {
result := NewSet()
for key := range s.elements {
if !other.Contains(key) {
result.Add(key)
}
}
return result
}
// Example usage of Difference
differenceSet := set1.Difference(set2)
fmt.Println("Difference:", differenceSet.List()) // ["apple"]
Performance
The time complexity for most operations (insertion, deletion, membership check) on the set is O(1), as they are backed by a map. This means they are a great choice if they match your use case!
Wrapping up
Hope you found this useful! You may also enjoy our other blogs here with plenty more Go tips!