특정 속성에 대한 LINQ의 Distinct ()


1094

나는 LINQ와 함께 그것에 대해 배우고 있지만 Distinct간단한 목록이없는 경우 사용하는 방법을 알 수 없습니다 (간단한 정수 목록은 수행하기가 쉽습니다. 이것은 문제가 아닙니다). 나는 희망을 사용하는 경우는 어떻게 고유 의 개체 목록에 하나 개 또는 더 많은 개체의 속성?

예 : 객체 인 경우 Person속성, Id. 모든 Person을 얻고 객체 Distinct의 속성 Id으로 어떻게 사용할 수 있습니까?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

어떻게 난 그냥 얻을 수 Person1Person3? 가능합니까?

LINQ를 사용할 수 없다면 Person.NET 3.5의 일부 속성에 따라 목록을 작성하는 가장 좋은 방법은 무엇입니까?

답변:


1246

편집 : 이것은 이제 MoreLINQ의 일부입니다 .

당신이 필요로하는 것은 효과적으로 "별명"입니다. 작성하기가 쉽지만 LINQ의 일부라고 생각하지 않습니다.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

Id속성 만 사용하여 고유 한 값을 찾으려면 다음을 사용할 수 있습니다.

var query = people.DistinctBy(p => p.Id);

여러 속성을 사용하려면 익명 유형을 사용하면 동등성을 적절하게 구현할 수 있습니다.

var query = people.DistinctBy(p => new { p.Id, p.Name });

테스트되지 않았지만 작동해야합니다 (그리고 이제 적어도 컴파일됩니다).

키에 대한 기본 비교자를 가정합니다. 만약 당신이 동등 비교기를 전달하고 싶다면, 그것을 HashSet생성자 에게 전달하십시오 .



1
@ ashes999 : 무슨 말인지 모르겠습니다. 코드는 의존성에 만족하는지 여부에 따라 답변 라이브러리에 있습니다.
Jon Skeet

10
@ ashes999 : 한 곳에서만이 작업을 수행하는 경우 사용 GroupBy이 더 간단합니다. 둘 이상의 장소에서 필요한 경우 의도를 캡슐화하는 것이 훨씬 더 깨끗합니다 (IMO).
Jon Skeet

5
@ MatthewWhited : IQueryable<T>여기에 언급이 없다는 것을 감안할 때 그것이 어떻게 관련되는지 알 수 없습니다. 나는 이것이 EF 등에 적합하지 않을 것에 동의하지만, LINQ to Objects 내에서는 그것이 보다 적합 하다고 생각합니다 GroupBy. 질문의 맥락은 항상 중요합니다.
Jon Skeet

7
이 프로젝트는 github로 이전했습니다. DistinctBy 코드는 다음과 같습니다. github.com/morelinq/MoreLINQ/blob/master/MoreLinq/DistinctBy.cs
Phate01

1858

나는에 따라 별개의 목록을 얻기 위해 무엇을하려는 경우 하나 개 또는 더 많은 속성을?

단순한! 당신은 그들을 그룹화하고 그룹에서 승자를 선택합니다.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

여러 속성에 그룹을 정의하려면 다음과 같이하십시오.

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

1
@ErenErsonmez입니다. 게시 된 코드를 사용하여 지연된 실행이 필요한 경우 ToList 호출을 중단하십시오.
Amy B

5
아주 좋은 답변입니다! Realllllly는 Linq-to-Entities에서보기를 수정할 수없는 SQL보기에서 구동되었습니다. First () 대신 FirstOrDefault ()를 사용해야했습니다. 모두 좋습니다.
Alex KeySmith

8
나는 그것을 시도하고 그것은 Select (g => g.FirstOrDefault ())로 변경해야합니다

26
@ChocapicSz Nope. 모두 Single()SingleOrDefault()각 던져 소스는 하나 개 이상의 항목이있는 경우. 이 작업에서는 각 그룹에 둘 이상의 항목이있을 수 있습니다. 그 문제를 들어, First()보다 선호되고 FirstOrDefault()각 그룹은 적어도 하나의 멤버가 있어야하기 때문에 각 그룹은 적어도 하나의 멤버 및 요구 사항을 가지고 알아낼 수있는 EntityFramework을 사용하지 않는 ... FirstOrDefault().
Amy B

2
FirstOrDefault() github.com/dotnet/efcore/issues/12088을 사용하더라도 EF Core에서 현재 지원되지 않는 것 같습니다 . 3.1에 있고 "번역 할 수 없습니다"오류가 발생합니다.
Collin M. Barrett

78

사용하다:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

where당신이 항목을 필터링하는 데 도움이 (더 복잡 할 수있다)과 groupby와는 select별개의 기능을 수행한다.


1
완벽하고 Linq를 확장하거나 다른 의존성을 사용하지 않고 작동합니다.
DavidScherer

77

모든 LINQ와 같이 보이게하려면 쿼리 구문을 사용할 수도 있습니다.

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

4
흠 내 생각은 쿼리 구문과 유창한 API 구문 모두 서로 마찬가지로 LINQ이며 사람들이 사용하는 것보다 선호도가 높습니다. 나는 그 이상 LINK-처럼 생각 것이다 그래서 나 자신은 유창하게 API를 선호하지만 난 그 주관적인 추측
최대 캐롤

LINQ-Like는 환경 설정과 아무런 관련이 없으며 "LINQ-like"는 C #에 포함 된 다른 쿼리 언어와 관련이 있습니다 .Java 스트림에서 나오는 유창한 인터페이스를 선호하지만 LINQ와는 다릅니다.
Ryan The Leach

우수한!! 너는 나의 영웅이야!
Farzin Kanzi 2009 년

63

충분하다고 생각합니다.

list.Select(s => s.MyField).Distinct();

43
그가 특정 분야 만이 아니라 자신의 모든 대상을 되 찾아야한다면 어떻게해야합니까?
Festim Cahani

1
속성 값이 동일한 여러 개체 중 정확히 어떤 개체입니까?
donRumatta

40

솔루션을 먼저 필드별로 그룹화 한 다음 firstordefault 항목을 선택하십시오.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

26

표준으로이 작업을 수행 할 수 있습니다 Linq.ToLookup(). 각 고유 키에 대한 값 모음이 생성됩니다. 컬렉션에서 첫 번째 항목을 선택하십시오.

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

17

다음 코드는 Jon Skeet의 답변 과 기능적으로 동일합니다 .

.NET 4.5에서 테스트되었으며 이전 버전의 LINQ에서 작동해야합니다.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Google 코드 에서 Jon Skeet의 최신 버전 인 DistinctBy.cs를 확인하십시오 .


3
이것은 "시퀀스에 값이 없습니다"라는 오류를 주었지만 Skeet의 대답은 올바른 결과를 낳았습니다.
멋진 것

10

Distinct 기능을 확장하여 다음과 같이 할 수있는 방법을 설명하는 기사를 작성했습니다.

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

이 기사는 다음과 같습니다. LINQ 확장-고유 함수에서 속성 지정


3
기사에 오류가 있습니다. Distinct 뒤에 <T>가 있어야합니다 : public static IEnumerable <T> Distinct (this ... 또한 하나 이상의 속성, 즉 첫 번째 조합에서 작동하는 것처럼 보이지 않습니다) 이름과 성.
ROW1

2
+1, 사소한 오류는 다운 보트를위한 충분한 이유가 아니며, 너무 어리석기 때문에 오타가 자주 발생했습니다. 그리고 나는 여전히 많은 속성에서 작동하는 일반적인 함수를 보지 못했습니다! downvoter 가이 스레드에서 다른 모든 답변을 downvoted하기를 바랍니다. 그러나이 두 번째 유형은 무엇입니까? 반대합니다 !
nawfal

4
귀하의 링크가 깨진
톰 린트를

7

개인적으로 나는 다음 수업을 사용합니다.

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

그런 다음 확장 방법 :

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

마지막으로 의도 된 사용법 :

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

이 접근법을 사용하여 찾은 이점은 LambdaEqualityComparer을 허용하는 다른 메소드 에 클래스를 재사용 한다는 것 IEqualityComparer입니다. (아, 나는 yield원래 LINQ 구현에 물건을 남겨 둡니다 ...)


5

여러 속성에 대해 고유 한 방법이 필요한 경우 PowerfulExtensions 라이브러리를 확인할 수 있습니다 . 현재는 매우 어린 단계이지만 이미 많은 속성을 제외하고 Distinct, Union, Intersect, 같은 방법을 사용할 수 있습니다.

이것은 당신이 그것을 사용하는 방법입니다 :

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

5

프로젝트에서 이러한 작업에 직면했을 때 비교기를 작성하기 위해 작은 API를 정의했습니다.

따라서 유스 케이스는 다음과 같습니다.

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

API 자체는 다음과 같습니다.

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

자세한 내용은 LINQ의 IEqualityComparer 사이트에 있습니다 .


5

DistinctBy ()를 사용하여 객체 속성으로 Distinct 레코드를 가져올 수 있습니다. 다음 문장을 사용하기 전에 추가하십시오.

Microsoft.Ajax.Utilities 사용;

그런 다음 다음과 같이 사용하십시오.

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

여기서 'Index'는 데이터를 구별하려는 속성입니다.


4

다음과 같이 (번개는 아니지만) 할 수 있습니다.

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

즉, "같은 ID를 가진 다른 사람이 목록에없는 모든 사람을 선택하십시오."

당신의 예에서, 사람 3을 선택할 것임을 명심하십시오. 나는 앞의 두 가지 중에서 당신이 원하는 것을 어떻게 말해야할지 모르겠습니다.


4

DistinctBy기능 을 얻기 위해 MoreLinq 라이브러리를 프로젝트에 추가하지 않으려 Distinct는 경우 IEqualityComparer인수 를 취하는 Linq의 메소드 과부하를 사용하여 동일한 최종 결과를 얻을 수 있습니다 .

람다 구문을 사용하여 일반 클래스의 두 인스턴스에 대한 사용자 정의 비교를 수행하는 일반 사용자 정의 평등 비교 자 클래스를 작성하여 시작하십시오.

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

    public bool Equals(T x, T y)
    {
        return _comparison(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

그런 다음 기본 코드에서 다음과 같이 사용합니다.

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

짜잔! :)

위의 내용은 다음과 같습니다.

  • 부동산 Person.Id유형int
  • people컬렉션에 null 요소가 포함되어 있지 않습니다

컬렉션에 null이 포함되어 있으면 람다를 다시 작성하여 null을 확인하십시오.

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

편집하다

이 접근법은 Vladimir Nesterovsky의 답변과 비슷하지만 더 간단합니다.

Joel의 답변과 비슷하지만 여러 속성을 포함하는 복잡한 비교 논리를 허용합니다.

당신의 목적은 오직 다를 수 있습니다 경우에는 Id다음 다른 사용자가 당신이해야 할 모든 기본 구현을 오버라이드 (override)이라는 정답 준 GetHashCode()Equals()당신의 Person클래스를 한 후 바로 아웃 - 오브 - 박스 사용하는 Distinct()필터의 LINQ 방법을 모든 중복.


dictonary의 고유 항목 만 가져오고 싶습니다. 도움이 필요합니다.이 코드를 사용하고 있다면 TempDT가 없으면 아무것도 아닙니다 .m_ConcurrentScriptDictionary = TempDT.AsEnumerable.ToDictionary (Function (x) x.SafeField (fldClusterId, NULL_ID_VALUE), Function (y) y.SafeField (fldParamValue11, NULL_ID_VALUE))
RSB


1
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

당신은 Select() new Person대신에 의미 했습니까 new Player? 당신이 주문한다는 사실은 ID어떻게 든 Distinct()고유성을 결정하는 데 그 속성을 사용한다고 알려주지 않으므로 작동하지 않습니다.
BACON

1

Equals (object obj)GetHashCode () 메서드를 재정의 합니다.

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

그런 다음 전화하십시오.

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

그러나 GetHashCode ()는 (이름도 포함하여) 더 발전해야합니다.이 대답은 아마도 내 의견으로는 가장 좋습니다. 실제로 대상 논리를 보관하려면 GetHashCode ()를 재정의 할 필요가없고 Equals ()이면 충분하지만 성능이 필요한 경우 재정의해야합니다. 모든 비교 알고리즘은 먼저 해시를 확인하고 같으면 Equals ()를 호출하십시오.
Oleg Skripnyak

또한 Equals ()에서 첫 번째 줄은 "if (! (obj is Person)) return false"여야합니다. 그러나 가장 좋은 방법은 "var o = obj를 Person; if (o == null)이 false를 반환하는 경우"와 같은 형식으로 캐스팅 된 별도의 개체를 사용하는 것입니다. 다음 캐스팅없이 오와 평등을 확인
올렉 Skripnyak

1
이와 같은 동등성을 재정의하는 것은 개인의 평등이 하나 이상의 자산에서 결정될 것으로 기대하는 다른 프로그래머들에게 의도하지 않은 결과를 초래할 수 있기 때문에 좋은 생각이 아닙니다.
B2K

0

실제로 Person.id에서 Equals를 수행하려면 Equals on person을 대체 할 수 있어야합니다. 이것은 당신이 쫓는 행동을 초래할 것입니다.


-5

아래 코드로 시도하십시오.

var Item = GetAll().GroupBy(x => x .Id).ToList();

3
짧은 대답은 환영하지만 문제 뒤에 무슨 일이 일어나고 있는지 이해하려는 후자의 사용자에게는 큰 가치를 제공하지 않습니다. 문제를 일으키는 실제 문제와 해결 방법을 설명 할 시간을 내십시오. 감사합니다 ~
Hearen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.