Development Tip

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

yourdevel 2020. 9. 27. 14:07
반응형

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


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

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

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

Person1과 Person3을 어떻게 얻을 수 있습니까? 가능합니까?

LINQ에서 가능하지 않은 경우 Person.NET 3.5의 일부 속성에 따라 목록을 만드는 가장 좋은 방법은 무엇입니까?


편집 : 이것은 이제 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생성자 에 전달하면됩니다 .


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

단순한! 그들을 그룹화하고 그룹에서 승자를 선택하고 싶습니다.

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();

모든 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();

사용하다:

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별개의 기능을 수행한다.


충분하다고 생각합니다.

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

솔루션을 먼저 필드별로 그룹화 한 다음 먼저 또는 기본 항목을 선택합니다.

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

표준 Linq.ToLookup(). 이렇게하면 각 고유 키에 대한 값 모음이 생성됩니다. 컬렉션의 첫 번째 항목을 선택하기 만하면됩니다.

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

다음 코드는 기능적으로 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 버전을 확인하십시오 .


다음과 같이 할 수 있도록 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 확장-고유 함수에서 속성 지정


다음과 같이 할 수 있습니다.

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

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

당신의 예에서 그것은 단지 사람 3을 선택한다는 것을 명심하십시오. 나는 이전 두 사람 중에서 당신이 원하는 것을 어떻게 말할지 모르겠습니다.


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

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);

이 접근 방식을 사용하여 찾은 장점은 .NET LambdaEqualityComparerFramework를 허용하는 다른 메서드에 대해 클래스를 재사용 한다는 것 IEqualityComparer입니다. (오, 그리고 나는 yield원래 LINQ 구현에 남겨 둡니다 ...)


여러 속성에 대해 Distinct 메서드가 필요한 경우 내 PowerfulExtensions 라이브러리를 확인할 수 있습니다 . 현재는 아주 어린 단계에 있지만 이미 여러 속성에 대해 Distinct, Union, Intersect, Except와 같은 메서드를 사용할 수 있습니다.

사용 방법은 다음과 같습니다.

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

프로젝트에서 이러한 작업에 직면했을 때 비교기를 구성하는 작은 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 사이트에 있습니다 .


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

You begin by creating a generic custom equality comparer class that uses lambda syntax to perform custom comparison of two instances of a generic class:

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);
    }
}

Then in your main code you use it like so:

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));

Voila! :)

The above assumes the following:

  • Property Person.Id is of type int
  • The people collection does not contain any null elements

If the collection could contain nulls then simply rewrite the lambdas to check for null, e.g.:

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

EDIT

This approach is similar to the one in Vladimir Nesterovsky's answer but simpler.

It is also similar to the one in Joel's answer but allows for complex comparison logic involving multiple properties.

However, if your objects can only ever differ by Id then another user gave the correct answer that all you need to do is override the default implementations of GetHashCode() and Equals() in your Person class and then just use the out-of-the-box Distinct() method of Linq to filter out any duplicates.


You can use DistinctBy() for getting Distinct records by an object property. Just add the following statement before using it:

using Microsoft.Ajax.Utilities;

and then use it like following:

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

where 'Index' is the property on which i want the data to be distinct.


The best way to do this that will be compatible with other .NET versions is to override Equals and GetHash to handle this (see Stack Overflow question This code returns distinct values. However, what I want is to return a strongly typed collection as opposed to an anonymous type), but if you need something that is generic throughout your code, the solutions in this article are great.


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

Override Equals(object obj) and GetHashCode() methods:

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();
    }
}

and then just call:

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

You should be able to override Equals on person to actually do Equals on Person.id. This ought to result in the behavior you're after.


Please give a try with below code.

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

참고URL : https://stackoverflow.com/questions/489258/linqs-distinct-on-a-particular-property

반응형