[Go] Set 구현: map[T]struct{} 와 map[T]bool 방식 비교

반응형

Golang에서는 Set(집합) 자료구조를 직접 구현하여 사용하여야 합니다.
Set은 중복을 허용하지 않는 원소들의 모임이며, 멤버십 확인 및 집합 연산을 위해 자주 사용됩니다.

 

보통 Set 구현을 map[T]struct{}map[T]bool로 이용해서 합니다.

Set을 구현하는 두 가지 방식인 map[T]struct{}map[T]bool을 비교해보고자 합니다.

 

Map에 대한 내용은 아래 글에서 확인하시면 됩니다.

[Go] Map 활용하기

 

[Go] Map 활용하기

Map 초기화 Go 언어에서 Map은 make 함수를 사용하여 초기화할 수 있습니다. make 함수는 맵의 타입을 지정하고, 맵의 초기 크기를 지정할 수 있습니다. 만약 초기 크기를 지정하지 않으면, 기본적으

1minute-before6pm.tistory.com


두 방식 모두 유효한 방법이지만, 각각의 특징을 이해하고 해당 상황에 적합한 방식을 선택하는 것이 중요합니다.
이제 더 자세한 내용을 살펴보겠습니다.

 

 

 

 

map[T]struct{} 방식

map[T]struct{} 방식은 Set을 구현하기 위해 맵(Map)을 사용하고 값(Value)으로는 빈 구조체(struct{})를 활용하는 방식입니다. 다음은 고려해야 할 주요 사항입니다:

메모리 효율성

값으로 struct{}를 사용하는 것은 메모리 효율성을 보장합니다.
빈 구조체는 크기가 0이므로, 각 원소마다 추가적인 메모리가 낭비되지 않습니다.
이 방식은 특히 큰 Set이나 메모리 제약이 있는 환경에서 유리합니다.

 

키의 중요성 강조

map[T]struct{}를 사용함으로써, Set에서는 키만이 중요하다는 사실을 강조할 수 있습니다.
빈 구조체는 정보를 담지 않기 때문에, 키 자체가 원소를 나타낸다는 점을 명확하게 표현할 수 있습니다.
이는 코드를 더 의미롭고 이해하기 쉽게 만들어줍니다.

 

예시

type Set struct {
    data map[T]struct{}
}

func NewSet() *Set {
    s := &Set{
        data: make(map[T]struct{}),
    }
    return s
}

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

func (s *Set) Remove(element T) {
    delete(s.data, element)
}

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

func (s *Set) Size() int {
    return len(s.data)
}

Set에 원소를 추가할 때는 해당 키에 빈 구조체를 할당하면 됩니다: set[element] = struct{}{}
Set 원소 포함 여부를 확인할 때는 맵에서 해당 키가 존재하는지 확인하면 됩니다:_, exists := set[element]
원소를 삭제할 때는 delete 함수를 사용하면 됩니다: delete(set, element)

 

 

 

 

 

map[T]bool 방식

map[T]bool 방식은 Set을 구현하기 위해 맵(Map)을 사용하며 값(Value)으로는 boolean 타입인 true 또는 false를 활용합니다.

 

추가적인 메모리 사용

값으로 bool을 사용하는 것은 각 원소마다 추가적인 메모리를 사용하게 됩니다.
map[T]struct{} 방식에 비해 약간 더 많은 메모리를 사용하므로, 큰 Set을 다룰 때는 약간의 메모리 오버헤드가 발생할 수 있습니다.

 

추가 정보의 유연성:

map[T]bool 방식은 각 원소와 관련된 추가 정보를 연결할 수 있는 유연성을 제공합니다.
boolean 값 사용을 통해 원소와 관련된 보조 데이터를 저장하고 검색할 수 있습니다.
이는 원소별 속성이나 메타데이터가 필요한 경우에 유용할 수 있습니다.

 

예시

type Set struct {
    data map[T]bool
}

func NewSet() *Set {
    s := &Set{
        data: make(map[T]bool),
    }
    return s
}

func (s *Set) Add(element T) {
    s.data[element] = true
}

func (s *Set) Remove(element T) {
    delete(s.data, element)
}

func (s *Set) Contains(element T) bool {
    exists := s.data[element]
    return exists
}

func (s *Set) Size() int {
    return len(s.data)
}

원소를 Set에 추가하기 위해 해당 키에 true 값을 할당합니다: set[element] = true.
Set 원소 포함 여부를 확인하기 위해서는 키에 연결된 boolean 값을 확인하면 됩니다: exists := set[element].
원소를 삭제하는 방법은 map[T]struct{} 방식과 유사합니다: delete(set, element)를 사용하면 됩니다.

 

 

결론

Golang에서 Set을 구현할 때, map[T]struct{}map[T]bool 방식은 모두 유효한 선택지입니다.
하지만 저는 주로 map[T]bool 방식보다는 map[T]struct{} 방식을 선호하는 편입니다.

 

 

참고 

https://itnext.io/set-in-go-map-bool-and-map-struct-performance-comparison-5315b4b107b

https://www.reddit.com/r/golang/comments/qzha6o/for_sets_maptstruct_vs_maptbool/