ImmutableHashSet

ImmutableHashSet<T>는 .NET에서 제공하는 불변 컬렉션Immutable Collections 중 하나로, 변경 불가능한 해시셋을 관리합니다. 불변 해시셋은 생성된 이후에 상태가 변경되지 않으며, 데이터의 추가나 삭제가 필요할 때마다 새로운 해시셋을 생성합니다. 이러한 특성은 데이터 무결성을 보장하고, 멀티스레드 환경에서의 동시성 문제를 방지하는 데 큰 장점이 있습니다.

불변 해시셋의 특징

  • 변경 불가능성: 해시셋이 생성된 이후에는 상태가 변경되지 않습니다. 데이터를 추가하거나 삭제할 때마다 새로운 해시셋이 생성됩니다.
  • 구조적 공유: 새로운 해시셋을 생성할 때 전체 데이터를 복사하지 않고, 변경된 부분만 복사하여 메모리 사용을 최적화합니다.
  • 스레드 안전성: 불변 해시셋은 멀티스레드 환경에서 안전하게 사용할 수 있어, 동기화에 대한 고민을 덜어줍니다.

기본 사용법

using System;
using System.Collections.Immutable;
class Program
{
    static void Main()
    {
        ImmutableHashSet<int> hashSet = ImmutableHashSet<int>.Empty;
        hashSet = hashSet.Add(1);
        hashSet = hashSet.Add(2);
        hashSet = hashSet.Add(3);
        Console.WriteLine(string.Join(", ", hashSet));
        // 출력: 1, 2, 3
        hashSet = hashSet.Remove(2);
        Console.WriteLine(string.Join(", ", hashSet));
        // 출력: 1, 3
    }
}
  • ImmutableHashSet<int>.Empty를 사용하여 빈 해시셋을 생성합니다.
  • Add를 호출하여 요소를 추가하면 새로운 해시셋이 반환됩니다.
  • Remove를 호출하여 요소를 제거하면 새로운 해시셋이 반환됩니다.
  • 기존 해시셋은 변경되지 않고 그대로 유지됩니다.

주요 메서드와 활용

생성 및 초기화

// 빈 해시셋 생성
ImmutableHashSet<int> emptyHashSet = ImmutableHashSet<int>.Empty;
// 요소를 추가하여 해시셋 생성
ImmutableHashSet<int> hashSet = emptyHashSet.Add(1).Add(2).Add(3);
// 컬렉션 초기화
var initialHashSet = ImmutableHashSet.Create(1, 2, 3);

요소 추가 및 제거

  • Add: 해시셋에 요소를 추가하여 새로운 해시셋을 반환합니다.
var newHashSet = hashSet.Add(4);
  • AddRange: 여러 요소를 한꺼번에 추가합니다.
var newHashSet = hashSet.AddRange(new[] { 5, 6, 7 });
  • TryAdd: 요소를 추가하고, 추가 성공 여부를 반환합니다. 요소가 이미 존재하는 경우 false를 반환합니다.
bool added = hashSet.TryAdd(4);
// 추가 성공 시 true, 이미 존재하는 경우 false 반환
  • Remove: 특정 요소를 제거하여 새로운 해시셋을 반환합니다.
var updatedHashSet = hashSet.Remove(2);
  • RemoveRange: 여러 요소를 제거합니다.
var updatedHashSet = hashSet.RemoveRange(new[] { 1, 3 });

요소 접근 및 수정

  • Contains: 해시셋에 특정 요소가 포함되어 있는지 여부를 확인합니다.
bool contains = hashSet.Contains(2);
// contains는 true 또는 false입니다.
  • GetValueOrDefault: 특정 요소를 찾고, 존재하지 않을 경우 기본값을 반환합니다.
int value = hashSet.GetValueOrDefault(2, -1);
// 요소가 존재하면 해당 값을, 존재하지 않으면 -1을 반환합니다.
bool contains = hashSet.Contains(2);
  • Clear: 모든 요소를 제거하고 빈 해시셋을 반환합니다.
var clearedHashSet = hashSet.Clear();
  • ToBuilder : 변경 가능한 ImmutableHashSet.Builder로 변환하여 여러 번의 변경 작업을 효율적으로 수행한 후, 다시 불변 리스트로 변환합니다.
var builder = hashSet.ToBuilder();
builder.Add(4);
builder.Add(5);
var updatedHashSet = builder.ToImmutable();
// 기존 해시셋은 변경되지 않으며, 새로운 해시셋에 4와 5가 추가됩니다.

집합 연산

ImmutableHashSet은 다양한 집합 연산을 지원합니다.

  • Union: 두 해시셋을 합집합하여 새로운 해시셋을 반환합니다.
    ImmutableHashSet<int> set1 = ImmutableHashSet.Create(1, 2, 3);
    ImmutableHashSet<int> set2 = ImmutableHashSet.Create(3, 4, 5);
    ImmutableHashSet<int> unionSet = set1.Union(set2);
    // unionSet은 1, 2, 3, 4, 5를 포함합니다.
  • Intersect: 두 해시셋의 교집합을 반환합니다.
    ImmutableHashSet<int> intersectSet = set1.Intersect(set2);
    // intersectSet은 3을 포함합니다.
  • Except: 첫 번째 해시셋에서 두 번째 해시셋에 포함된 요소를 제외한 새로운 해시셋을 반환합니다.
    ImmutableHashSet<int> exceptSet = set1.Except(set2);
    // exceptSet은 1, 2를 포함합니다.
  • SymmetricExcept: 두 해시셋의 대칭 차집합을 반환합니다.
    ImmutableHashSet<int> symmetricSet = set1.SymmetricExcept(set2);
    // symmetricSet은 1, 2, 4, 5를 포함합니다.

고급 활용과 최적화

멀티스레드 환경에서 중복 제거된 데이터 수집

멀티스레드 환경에서 데이터 수집 시 각 스레드가 중복 없이 데이터를 추가하고, 수집된 데이터의 일관성을 유지해야 하는 경우 ImmutableHashSet<T>를 활용할 수 있습니다. 불변 특성 덕분에 여러 스레드가 데이터를 추가해도 데이터가 변경되지 않으므로 안전하게 중복을 제거할 수 있습니다.

using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
public class ConcurrentDataCollector<T>
{
    private ImmutableHashSet<T> _dataSet = ImmutableHashSet<T>.Empty;
    public void AddData(T data)
    {
        ImmutableInterlocked.Update(ref _dataSet, set => set.Add(data));
    }
    public ImmutableHashSet<T> GetData()
    {
        return _dataSet;
    }
}
// 사용 예제
var collector = new ConcurrentDataCollector<int>();
Parallel.For(0, 100, i =>
{
    collector.AddData(i % 10); // 일부 중복 데이터를 추가하여 중복 제거 기능 테스트
});
var data = collector.GetData();
foreach (var item in data)
{
    Console.WriteLine(item);
}
// 출력 예:
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 (중복 없이 고유 값만 포함)
  • ImmutableInterlocked.Update를 사용하여 스레드 안전하게 데이터를 추가합니다.
  • 중복이 자동으로 제거되므로, 추가적인 중복 체크 없이 고유 데이터만 수집됩니다.

대규모 데이터의 고유성 유지 및 변경 이력 관리

ImmutableHashSet<T>는 데이터가 변경될 때마다 새로운 인스턴스를 생성하므로, 변경 전후의 고유한 데이터 상태를 기록하여 필요할 때 과거의 고유 상태를 쉽게 복원할 수 있습니다.

using System;
using System.Collections.Immutable;
public class DataHistoryManager<T>
{
    private ImmutableList<ImmutableHashSet<T>> _history = ImmutableList<ImmutableHashSet<T>>.Empty;
    public void SaveCurrentState(ImmutableHashSet<T> currentState)
    {
        _history = _history.Add(currentState);
    }
    public ImmutableHashSet<T> GetStateAt(int version)
    {
        if (version < 0 || version >= _history.Count)
            throw new ArgumentOutOfRangeException(nameof(version), "Invalid version");
        return _history[version];
    }
}
// 사용 예제
var manager = new DataHistoryManager<int>();
var initialState = ImmutableHashSet<int>.Empty.Add(1).Add(2);
manager.SaveCurrentState(initialState);
var updatedState = initialState.Add(3).Add(4);
manager.SaveCurrentState(updatedState);
var finalState = updatedState.Remove(1);
manager.SaveCurrentState(finalState);
// 특정 상태 복원
var snapshot = manager.GetStateAt(1);
foreach (var item in snapshot)
{
    Console.WriteLine(item);
}
// 출력:
// 1, 2, 3, 4
  • 각 상태를 ImmutableHashSet<T>로 저장하여 변경 이력을 관리합니다.
  • 특정 시점의 데이터 상태를 복원할 수 있습니다.

다중 데이터 소스의 중복 없는 병합

여러 데이터 소스에서 수집된 데이터를 중복 없이 통합해야 하는 경우, ImmutableHashSet<T>는 각 데이터 소스를 안전하게 병합하면서 중복 요소를 자동으로 제거해 줍니다.

using System;
using System.Collections.Immutable;
public class DataMerger<T>
{
    public ImmutableHashSet<T> MergeSources(params ImmutableHashSet<T>[] sources)
    {
        var mergedSet = ImmutableHashSet<T>.Empty;
        foreach (var source in sources)
        {
            mergedSet = mergedSet.Union(source);
        }
        return mergedSet;
    }
}
// 사용 예제
var source1 = ImmutableHashSet<int>.Empty.Add(1).Add(2).Add(3);
var source2 = ImmutableHashSet<int>.Empty.Add(2).Add(3).Add(4);
var source3 = ImmutableHashSet<int>.Empty.Add(4).Add(5);
var merger = new DataMerger<int>();
var mergedData = merger.MergeSources(source1, source2, source3);
foreach (var item in mergedData)
{
    Console.WriteLine(item);
}
// 출력:
// 1, 2, 3, 4, 5
  • Union 메서드를 사용하여 여러 해시셋을 병합합니다.
  • 중복된 요소는 자동으로 제거됩니다.

고성능 필터링 및 검색 최적화

ImmutableHashSet<T>는 해시 기반의 내부 구조를 가지고 있으므로 고유한 데이터 집합을 빠르게 필터링하고 검색할 수 있습니다.

using System;
using System.Collections.Immutable;
using System.Linq;
public class DataFilter<T>
{
    private ImmutableHashSet<T> _dataSet;
    public DataFilter(ImmutableHashSet<T> dataSet)
    {
        _dataSet = dataSet;
    }
    public ImmutableHashSet<T> Filter(Func<T, bool> predicate)
    {
        return _dataSet.Where(predicate).ToImmutableHashSet();
    }
}
// 사용 예제
var dataSet = ImmutableHashSet<int>.Empty.Add(1).Add(2).Add(3).Add(4).Add(5);
var filter = new DataFilter<int>(dataSet);
var filteredData = filter.Filter(x => x > 2);
foreach (var item in filteredData)
{
    Console.WriteLine(item);
}
// 출력:
// 3, 4, 5
  • LINQ와 ImmutableHashSet<T>를 조합하여 효율적인 필터링을 수행합니다.
  • 해시 기반의 검색으로 대규모 데이터에서도 빠른 성능을 보입니다.