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());
좋아요 2