본문 바로가기
Programming/C# & .NET

[C#] Distinct(), DistinctBy() 중복제거

by Kor-IT 2024. 1. 10.
반응형

 

Distinct, DistinctBy

 

 

Distinct()


정의

namespace : Systme.Linq
assembly : Systme.Linq.dll

 

시퀀스의 고유 요소를 반환한다.

 

Overloads

Distinct<TSource>(IEnumerable<TSource>, IEqualityComparer<TSource>) 지정된 IEqualityComparer<T>로 값을 비교(필터)하여 시퀀스에서 고유 요소를 반환된다.
Distinct<TSource>(IEnumerable<TSource>) 기본 '같음' 비교자로 값을 비교하여 시퀀스에서 고유 요소를 반환한다.
❗비교값 반환 시 첫 번째 값이 반환된다.
⭐지연실행

 

 

 

Distinct<TSource>(IEnumerable<TSource>, IEqualityComparer<TSource>)


지정된 IEqualityComparer<T>로 값을 비교(필터)하여 시퀀스에서 교유 요소를 반환한다.

public static System.Collections.Generic.IEnumerable<TSource> Distinct<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, System.Collections.Generic.IEqualityComparer<TSource>? comparer);

 


예제

public class School
{
    public string Name { get; set; }

    public string Address { get; set; }
}

public class CustomSchoolEqualityComparer : IEqualityComparer<School>
{
    // 반환(필터) 하고자 하는 로직 구성
    public bool Equals(School? x, School? y)
    {
        if(x.Name.Equals(y.Name)) return true;
        else return false;
    }

    // Equals() 가 true를 반환하면 GetGashCode()는 해당 객체에 대해 동일한 값을 반환해야 한다.
    public int GetHashCode([DisallowNull] School obj)
    {
        return obj.Name.GetHashCode();
    }
}

 

위의 코드에서 School Class로 'IEqualityComparer'를 구현했다. 상속받은 Euqlas(), GetHashCode를 정의했다.

 

School[] schools = { 
    new School(){ Name = "school1", Address = "address1"},
    new School(){ Name = "school2", Address = "address2"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school5", Address = "address5"},
    new School(){ Name = "school3", Address = "address31"},
    new School(){ Name = "school4", Address = "address41"},
};

IEnumerable<School> noduplicates = schools.Distinct(new CustomSchoolEqualityComparer());

foreach(School school in noduplicates)
{
    Console.WriteLine($"name : {school.Name} / address : {school.Address}");
}

 

[Output]

name : school1 / address : address1
name : school2 / address : address2
name : school3 / address : address3
name : school4 / address : address4
name : school5 / address : address5

 

설명

name이 같은 School 객체는 첫 번째 값이 반환되어 school3, school4 의address 도 첫번째 값이 반환되고 있다.

 

 

 

Distinct<TSource>(IEnumerable<TSource>)


기본적으로 자주 사용되는 형식이며, 기본적으로 '같음' 비교자로 값을 비교하여 시퀀스에서 고유 요소를 반환한다.

 

예제

 

School[] schools = { 
    new School(){ Name = "school1", Address = "address1"},
    new School(){ Name = "school2", Address = "address2"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school5", Address = "address5"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school3", Address = "address31"},
    new School(){ Name = "school4", Address = "address41"},
};

IEnumerable<School> noduplicates = = schools.Distinct();

foreach (School school in noduplicates)
{
    Console.WriteLine($"name : {school.Name} / address : {school.Address}");
}

 

[Output]

name : school1 / address : address1
name : school2 / address : address2
name : school3 / address : address3
name : school4 / address : address4
name : school5 / address : address5
name : school3 / address : address3
name : school4 / address : address4
name : school3 / address : address31
name : school4 / address : address41

 

기본적으로 'Distinct'는 참조형식으로 중복제거를 진행되기 때문에 속성의 값이 동일하다고 해도 참조(주소)가 다르기 때문에 해당 예제에서는 모든 값들이 나오는 게 정상이다.

만약 동일한 속성값이 중복되길 원한다면 'IEqualityComparer'에 정의해서 호출하거나 아래 DistinctBy()로 쉽게 해결이 가능하다.

 

 

DistinctBy()


정의

namespace : Systme.Linq
assembly : Systme.Linq.dll

 

시퀀스의 고유 요소 지정된 키에 의해 반환한다.

 

Overloads

DistinctBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) 지정된 키 선택기 함수에 따라 시퀀스에서 고유 요소를 반환합니다.
DistinctBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IEqualityComparer<TKey>) 지정된 키 선택기 함수에 따라 시퀀스에서 고유 요소를 반환하고 지정된 비교자를 사용하여 키를 비교합니다.
❗비교값 반환 시 첫 번째 값이 반환된다.
⭐지연실행

 

 

DistinctBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>)


지정된 키 선택기 함수에 따라 시퀀스에서 고유 요소를 반환합니다.

public static System.Collections.Generic.IEnumerable<TSource> DistinctBy<TSource,TKey> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector);

 

예제

School[] schools = { 
    new School(){ Name = "school1", Address = "address1"},
    new School(){ Name = "school2", Address = "address2"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school5", Address = "address5"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school3", Address = "address31"},
    new School(){ Name = "school4", Address = "address41"},
};

IEnumerable<School> noduplicates = schools.DistinctBy(x => x.Name);

foreach (School school in noduplicates)
{
    Console.WriteLine($"name : {school.Name} / address : {school.Address}");
}

 

[Output]

name : school1 / address : address1
name : school2 / address : address2
name : school3 / address : address3
name : school4 / address : address4
name : school5 / address : address5

 

'Name'에 대해서 동일한 값이라면 'address'가 달라도 중복처리 된다. 중복이 되는 Key를 'Name' 속성으로 지정되어 있기 때문이다.

 

예제

IEnumerable<School> noduplicates = schools.DistinctBy(keySelector: x => (x.Name, x.Address)).ToList();

 

[Output]

name : school1 / address : address1
name : school2 / address : address2
name : school3 / address : address3
name : school4 / address : address4
name : school5 / address : address5
name : school3 / address : address31
name : school4 / address : address41

 

여러 속성에 대해서 중복처리 하고 싶다면 위의 코드처럼 괄호로 묶어 주면 된다.

 

 

DistinctBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IEqualityComparer<TKey>)


지정된 키 선택기 함수에 따라 시퀀스에서 고유 요소를 반환하고 지정된 비교자를 사용하여 키를 비교합니다.

public static System.Collections.Generic.IEnumerable<TSource> DistinctBy<TSource,TKey> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer);

 

 

예제

public class CustomSchoolNameEqualityComparer : IEqualityComparer<string>
{
    public bool Equals(string? x, string? y)
    {
        return string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode([DisallowNull] string obj)
    {
        return obj is null ? 0 : obj.ToLowerInvariant().GetHashCode();
    }
}

 

School[] schools = { 
    new School(){ Name = "school1", Address = "address1"},
    new School(){ Name = "school2", Address = "address2"},
    new School(){ Name = "school3", Address = "address3"},
    new School(){ Name = "school4", Address = "address4"},
    new School(){ Name = "school5", Address = "address5"},
    new School(){ Name = "SCHool3", Address = "address3"},
    new School(){ Name = "schOOL4", Address = "address4"},
    new School(){ Name = "SCHOOL3", Address = "address31"},
    new School(){ Name = "school4", Address = "address41"},
};

IEnumerable<School> noduplicates = schools.DistinctBy(keySelector: x => x.Name, comparer: new CustomSchoolNameEqualityComparer());

foreach (School school in noduplicates)
{
    Console.WriteLine($"name : {school.Name} / address : {school.Address}");
}

 

[Output]

name : school1 / address : address1
name : school2 / address : address2
name : school3 / address : address3
name : school4 / address : address4
name : school5 / address : address5

 

'IEqualityComparer' 상속받은 'CustomSchoolNameEqualityComparer'에 대소문자 구분 없이 비교 로직을 구성해 두었다.

복잡한 비교 로직이 있다면 ' IEqualityComparer' 상속받은 Class에 구성해서 사용하는 것이 좋을 것 같다.

 

 

 

 

반응형

댓글