HashSet

HashSet<T>는 중복된 요소를 허용하지 않는 제네릭 컬렉션으로, 해시테이블HashTable을 사용하여 데이터를 효율적으로 관리합니다. HashSet<T>는 특히 집합 연산에 유용하며, 중복 데이터를 쉽게 제거하고, 데이터 검색 및 삽입에서 높은 성능을 발휘합니다. System.Collections.Generic 네임스페이스에 포함되어 있으며, 데이터의 추가, 제거, 검색 등을 빠르게 수행할 수 있습니다.

HashSet의 기본 사용법

HashSet<T>를 사용하는 방법은 아래의 코드 예제를 통해 이해할 수 있습니다:

using System;
using System.Collections.Generic;
class Program
{
    static void Main()
    {
        HashSet<int> set = new HashSet<int>();
        set.Add(1);
        set.Add(2);
        set.Add(3);
        set.Add(3); // 중복 요소 추가 시 무시됨
        foreach (int number in set)
        {
            Console.WriteLine(number); // 1, 2, 3
        }
    }
}
  • Add: 데이터를 해시 집합에 추가합니다. 중복된 요소는 허용되지 않습니다.
  • Contains: 특정 요소가 집합에 있는지 확인합니다.
  • Remove: 특정 요소를 집합에서 제거합니다. 위 코드에서 Add(3)을 두 번 호출했지만, HashSet은 중복된 값을 허용하지 않으므로 한 번만 저장됩니다.

HashSet의 장점과 단점

장점

  • 중복 제거: 동일한 요소를 여러 번 추가하려고 해도 하나만 저장하므로 중복 제거가 쉽습니다.
  • 빠른 검색 및 삽입: 해시 테이블을 기반으로 하기 때문에 검색, 추가, 제거의 평균 시간 복잡도가 O(1)입니다.
  • 집합 연산 지원: 합집합, 교집합, 차집합 등의 집합 연산을 쉽게 수행할 수 있습니다.

단점

  • 메모리 사용: 해시 테이블을 사용하기 때문에 해시 충돌 관리 및 빈 슬롯 관리를 위한 추가 메모리 오버헤드가 발생할 수 있습니다.
  • 순서가 없음: HashSet<T>는 데이터의 순서를 보장하지 않기 때문에 순서가 중요한 경우 다른 컬렉션을 사용하는 것이 좋습니다.

HashSet의 주요 메서드와 속성

생성자와 초기화

기본 생성자

HashSet을 생성합니다.

HashSet<int> set = new HashSet<int>();

초기 용량 지정

해시 집합의 초기 용량을 설정하여 성능을 최적화할 수 있습니다.

HashSet<int> set = new HashSet<int>(capacity: 100);
  • 초기 용량 설정을 통한 최적화를 참조하세요.

다른 컬렉션을 이용한 생성

다른 컬렉션을 이용하여 HashSet을 생성할 수 있습니다.

List<int> list = new List<int> { 1, 2, 3, 3, 4 };
HashSet<int> set = new HashSet<int>(list);
// 결과: set에는 1, 2, 3, 4가 포함됨
  • 다른 컬렉션의 요소를 이용하여 HashSet을 생성해도 중복된 요소는 자동으로 제거됩니다.

요소 추가 및 제거

Add

집합에 요소를 추가합니다.

set.Add(4);
  • 중복된 요소를 추가하려고 하면 무시됩니다.

Remove

집합에서 특정 요소를 제거합니다.

set.Remove(2);
  • 제거에 성공하면 true를 반환하고, 요소가 없으면 false를 반환합니다.

집합함수

UnionWith

다른 집합과의 합집합을 만듭니다.

HashSet<int> otherSet = new HashSet<int> { 4, 5, 6 };
set.UnionWith(otherSet);
// 결과: set에는 1, 2, 3, 4, 5, 6이 포함됨

IntersectWith

다른 집합과의 교집합을 만듭니다.

HashSet<int> otherSet = new HashSet<int> { 2, 3, 4 };
set.IntersectWith(otherSet);
// 결과: set에는 2, 3이 포함됨

ExceptWith

다른 집합과의 차집합을 만듭니다.

HashSet<int> otherSet = new HashSet<int> { 3 };
set.ExceptWith(otherSet);
// 결과: set에는 1, 2가 포함됨

IsSubsetOf

현재 집합이 지정된 집합의 부분 집합인지 여부를 확인합니다.

HashSet<int> otherSet = new HashSet<int> { 1, 2, 3, 4 };
bool isSubset = set.IsSubsetOf(otherSet);

IsSupersetOf

현재 집합이 지정된 집합의 상위 집합인지 여부를 확인합니다.

HashSet<int> otherSet = new HashSet<int> { 1, 2 };
bool isSuperset = set.IsSupersetOf(otherSet);
  • 현재 집합이 다른 집합의 모든 요소를 포함하고 있는지, 즉 다른 집합이 현재 집합의 부분집합인지 확인합니다.

Overlaps

현재 집합과 지정된 컬렉션이 겹치는지 여부를 확인합니다.

HashSet<int> otherSet = new HashSet<int> { 3, 4, 5 };
bool hasOverlap = set.Overlaps(otherSet);
  • 두 컬렉션 간에 하나 이상의 요소가 겹칠 경우 true를 반환합니다.

SetEquals

현재 집합과 지정된 컬렉션이 같은지 여부를 확인합니다.

HashSet<int> otherSet = new HashSet<int> { 1, 2, 3 };
bool areEqual = set.SetEquals(otherSet);
  • 두 집합에 포함된 모든 요소가 동일한 경우 true를 반환합니다

기타 유용한 메서드와 속성

Contains

집합에 특정 요소가 포함되어 있는지 확인합니다.

bool hasValue = set.Contains(3);

Count

집합에 포함된 요소의 개수를 반환합니다.

int count = set.Count;

HashSet의 활용

중복 제거

HashSet<T>는 중복 요소를 허용하지 않기 때문에, 대량의 데이터에서 중복을 손쉽게 제거할 수 있습니다. 예를 들어, 배열에서 중복을 제거하고 고유한 값만 저장하려면 다음과 같이 사용할 수 있습니다:

int[] numbers = { 1, 2, 3, 3, 4, 5, 5, 6 };
HashSet<int> uniqueNumbers = new HashSet<int>(numbers);
foreach (int number in uniqueNumbers)
{
    Console.WriteLine(number); // 1, 2, 3, 4, 5, 6
}

집합 연산 수행

HashSet<T>는 합집합, 교집합, 차집합 등 집합 연산을 손쉽게 수행할 수 있습니다. 다음은 두 집합의 교집합을 구하는 예제입니다:

HashSet<int> setA = new HashSet<int> { 1, 2, 3, 4 };
HashSet<int> setB = new HashSet<int> { 3, 4, 5, 6 };
setA.IntersectWith(setB);
foreach (int number in setA)
{
    Console.WriteLine(number); // 3, 4
}

빠른 검색과 검사

HashSet<T>는 해시 테이블을 사용하기 때문에, 데이터의 존재 여부를 빠르게 확인할 수 있습니다. 예를 들어, 회원 가입 시스템에서 중복된 아이디를 검사할 때 유용합니다:

HashSet<string> userIds = new HashSet<string> { "user1", "user2", "user3" };
string newUserId = "user4";
if (userIds.Contains(newUserId))
{
    Console.WriteLine("이미 사용 중인 아이디입니다.");
}
else
{
    userIds.Add(newUserId);
    Console.WriteLine("아이디가 등록되었습니다.");
}

사용자 정의 객체와 HashSet

사용자 정의 클래스의 객체를 HashSet<T>에 저장할 때는 EqualsGetHashCode 메서드를 올바르게 구현해야 합니다. 이 두 메서드는 HashSet에서 요소의 동일성을 판단하는 데 사용되므로, 올바른 구현이 없다면 중복된 객체가 저장되거나 검색되지 않는 문제가 발생할 수 있습니다.

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override bool Equals(object obj)
    {
        if (obj is Person other)
        {
            return Name == other.Name && Age == other.Age;
        }
        return false;
    }
    public override int GetHashCode()
    {
        return HashCode.Combine(Name, Age);
    }
}

위 코드에서 EqualsGetHashCode를 재정의하여 동일한 이름과 나이를 가진 Person 객체가 HashSet에서 중복으로 저장되지 않도록 합니다.

스레드 안전성

HashSet<T>는 기본적으로 스레드 안전하지 않습니다. 여러 스레드에서 동시에 접근하는 경우 문제가 발생할 수 있으며, 이러한 상황에서는 외부에서 적절한 동기화를 적용해야 합니다. 또는 ConcurrentDictionary<TKey, TValue>와 같은 스레드 안전 컬렉션을 사용하는 것이 좋습니다.