IEqualityComparer<T> 제거 대상 설정

C#에서 Linq 사용 시 Collection에 대해 중복제거를 하고 싶다면 IEqualityComparer를 상속받은 클래스를 구현하여 Distinct() 사용 시 인자값으로 IEqualityComparer 객체를 넣어주게 됩니다.

이 때 조건을 특정 조건을 더 넣어서 제거되게 되는 대상을 특정할 수 있을까요?

using System.Diagnostics.CodeAnalysis;

class CustomItem
{
    public int Idx { get; set; }
    public int intValue { get; set; }
    public string strValue { get; set; }
    public long longValue { get; set;}
}

class CustomItemIntEqualityComparer : IEqualityComparer<CustomItem>
{
    public bool Equals(CustomItem x, CustomItem y)
    {
        if (x.intValue == y.intValue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public int GetHashCode([DisallowNull] CustomItem obj)
    {
        int hash = obj.intValue.GetHashCode();

        return hash;
    }
}

class CustomItemStringEqualityComparer : IEqualityComparer<CustomItem>
{
    public bool Equals(CustomItem x, CustomItem y)
    {
        if (x.strValue.Equals(y.strValue))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public int GetHashCode([DisallowNull] CustomItem obj)
    {
        int hash = obj.strValue.GetHashCode();

        return hash;
    }
}

class CustomItemLongEqualityComparer : IEqualityComparer<CustomItem>
{
    public bool Equals(CustomItem x, CustomItem y)
    {
        if (x.longValue == y.longValue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public int GetHashCode([DisallowNull] CustomItem obj)
    {
        int hash = obj.longValue.GetHashCode();

        return hash;
    }
}

List<CustomItem> list = new()
{
    new CustomItem
    {
        Idx = 0,
        intValue = 1,
        strValue = "일루수가 누구야",
        longValue = 100,
    },
    new CustomItem
    {
        Idx = 1,
        intValue = 1,
        strValue = "이루수가 누구야",
        longValue = 200,
    },
    new CustomItem
    {
        Idx = 2,
        intValue = 1,
        strValue = "일루수가 누군데",
        longValue = 200,
    },
    new CustomItem
    {
        Idx = 3,
        intValue = 1,
        strValue = "이루수가 누구야",
        longValue = 400,
    },
};

Console.WriteLine("[INT]");
List<CustomItem> intDistinctList = list.Distinct(new CustomItemIntEqualityComparer()).ToList();

foreach (CustomItem item in intDistinctList)
{
    Console.WriteLine($"Idx: {item.Idx}, int: {item.intValue}, string: {item.strValue}, long: {item.longValue}");
}

Console.WriteLine("--------------------------------------------------------------------------------------------------");
Console.WriteLine("[STRING]");
List<CustomItem> strDistinctList = list.Distinct(new CustomItemStringEqualityComparer()).ToList();

foreach (CustomItem item in strDistinctList)
{
    Console.WriteLine($"Idx: {item.Idx}, int: {item.intValue}, string: {item.strValue}, long: {item.longValue}");
}

Console.WriteLine("--------------------------------------------------------------------------------------------------");
Console.WriteLine("[LONG]");
List<CustomItem> longDistinctList = list.Distinct(new CustomItemLongEqualityComparer()).ToList();

foreach (CustomItem item in longDistinctList)
{
    Console.WriteLine($"Idx: {item.Idx}, int: {item.intValue}, string: {item.strValue}, long: {item.longValue}");
}

Console.WriteLine("--------------------------------------------------------------------------------------------------");

이 코드를 실행하면 매번 같은 결과가 출력됩니다.
여기서 첫번째 int값으로 중복제거를 할 때 Idx가 0번인 값 이외의 값이 출력되도록 할 수 있나요?

소스는 vscode에서 .NET Interactive에서 실행하시거나, .NET 5 Console APP에서 실행하시면 됩니다.

2개의 좋아요

저도 예전에 고민했었던 문제네요…

일단 IEqualityComparer는 진짜 이름 그대로 같은지 확인만 합니다.
그래서 Distinct를 하면 IEqualityComparer에 의해 정확히 같은 객체라고 판단한 것만 필터링하는데, 여기에 추가 조건을 걸고 싶다면 직접 구현하셔야 할 거에요…

지금 코딩할 수 있는 환경이 아니라서 코드 작성은 못하지만 간단하게 생각나는 방법으로는 다음과 같습니다,

public ICollection<CustomItem> Filter(IEnumerable<CustomItem> items)
{
    var cache = new Dictionary<int, CustomItem>();
    var comparer = new CustomItemEqualityComparer();

    foreach (var item in items)
    {
        var hash = comparer.GetHashCode(item);

        if (cache.TryGetValue(hash, out var cacheItem) &&
            comparer.Equals(item, cacheItem) &&
            cacheItem.Idx != 0)
        {
            continue;
        }
        else
        {
            cache[hash] = item;
        }
    }

    return cache.Values;
}
2개의 좋아요

안녕하세요.

저 같은 경우는 ICollectionView를 사용해서 직접 필터 처리를 구현했습니다.

var collectionView = CollectionViewSource.GetDefaultView(원본 컬렉션);
collectionView.Filter = this.CollectionViewFilter;
collectionView.Refresh();

private bool CollectionViewFilter(object item)
{
    // 필터 로직 구현
}

위 처럼 단순 값 비교 후 중복제거가 아닌 추가 조건이 필요한 경우
직접 필터 처리를 구현하여 처리 할 수 있습니다.

2개의 좋아요

말씀하신대로 중복제거의 개념보다는 필터링 개념으로 가야 맞겠군요…다시 찾아보겠습니다. 감사합니다.

1개의 좋아요

CollectionViewSource 라는 것을 사용하려면 System.Windows.Data 네임스페이스를 참조해야하는군요… WPF FCL인 것 같습니다…뭐 참조하려면 하겠지만 저는 콘솔에서 사용하려고 해서요… 감사합니다!!

2개의 좋아요

기존에 사용하던 이 예제에 대한 필터링은 Linq를 이용해 진행했었습니다.
하지만 메서드가 많이 호출되고, 가비지가 많이 나오는 것 같아 다른 방법을 찾아보고 있었는데 역시 수동구현하면서 편하게 작업하려면 기존 방식대로 해야할 듯 싶습니다.

IEnumerable<IGrouping<string, CustomItem>> list = temp.GroupBy(e => e.strValue).Where(e => e.Count() > 1);
IEnumerable<List<CustomItem>> data = list.Select(e => e.ToList());
3개의 좋아요