Map-as-set in go

In programming languages like go, there is no built-in type for set. A set essentially answers yes or no: is a given element present?

We can use the built-in map type to implement sets.

I used to implement map-as-set like this:

// assuming the element type in the set is string

set := map[string]bool{
    "apple": true,
    "banana": true,
    "cherry": true,
}

given := "banana"

// is "banana" in the set?
if set[given] {
    // "banana" is in the set
}

Apparently, Gemini thinks there’s a better way:

// assuming the element type in the set is string

set := map[string]struct{}{
    "apple": struct{}{},
    "banana": struct{}{},
    "cherry": struct{}{},
}

given := "banana"

// is "banana" in the set?
if _, ok := set[given]; ok {
    // "banana" is in the set
}

Gemini thinks this is better because it saves memory. a bool value takes 1 byte, but an empty object struct{}{} takes 0 bytes; however, we can no longer assess the return value in a boolean manner, because it doesn’t matter whether the key is present or not present; the first return value will always be an empty object struct{}{} which is ambiguous af; therefore, an explicit check on the 2nd return value (i.e. ok in this case) is necessary.

Let’s implement a full-blown custom Set type.

type Set[T comparable] struct {
	kv map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
	return &Set[T]{
		kv: make(map[T]struct{}),
	}
}

func (s *Set[T]) Add(element T) {
	s.kv[element] = struct{}{}
}

func (s *Set[T]) Remove(element T) {
	delete(s.kv, element)
}

func (s *Set[T]) Contains(element T) bool {
	_, found := s.kv[element]
	return found
}

func (s *Set[T]) Len() int {
	return len(s.kv)
}

func (s *Set[T]) Clear() {
	clear(s.kv)
}

Ok bye.

Leave a Reply