람다와 구별 되는가?


746

그렇습니다. 나는 열거 할 수 있고 명확한 가치를 얻고 싶습니다.

를 사용하면 System.Linq물론이라는 확장 방법이 Distinct있습니다. 간단한 경우 다음과 같이 매개 변수없이 사용할 수 있습니다.

var distinctValues = myStringList.Distinct();

좋고 훌륭하지만 동등성을 지정 해야하는 열거 가능한 객체가있는 경우 사용 가능한 유일한 과부하는 다음과 같습니다.

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

항등 비교 자 인수는의 인스턴스 여야합니다 IEqualityComparer<T>. 물론이 작업을 수행 할 수는 있지만 다소 장황하고 음탕합니다.

내가 기대했던 것은 람다가 걸리는 과부하입니다 .Func <T, T, bool> :

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

그러한 확장이 있는지 또는 동등한 해결 방법이 있는지 아는 사람이 있습니까? 아니면 뭔가 빠졌습니까?

또는 IEqualityComparer 인라인을 지정하는 방법이 있습니까?

최신 정보

Anders Hejlsberg 가이 주제에 대한 MSDN 포럼 의 게시물 에 대한 답변을 찾았습니다 . 그는 말한다 :

두 객체가 동일하게 비교 될 때 동일한 GetHashCode 반환 값을 가져야합니다. 그렇지 않으면 Distinct에서 내부적으로 사용되는 해시 테이블이 올바르게 작동하지 않습니다. IEqualityComparer는 Equals 및 GetHashCode의 호환 가능한 구현을 단일 인터페이스로 패키지하기 때문에 IEqualityComparer를 사용합니다.

나는 그것이 의미가 있다고 생각합니다 ..


2
GroupBy를 사용하는 솔루션에 대해서는 stackoverflow.com/questions/1183403/… 을 참조하십시오

17
Anders Hejlsberg 업데이트에 감사드립니다!
Tor Haugen

아니, 그것은 이해가되지 않습니다-어떻게 같은 값을 가진 두 개의 객체가 두 개의 다른 해시 코드를 반환 할 수 있습니까?
GY

그것은 도움이 될 수 - 솔루션 에 대한 .Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId)), 그리고 GetHashCode ()가 제대로 작동하려면 중요한 이유를 설명한다.
marbel82

답변:


1028
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

12
우수한! 이것은 확장 방법으로 캡슐화하기도 쉽습니다 DistinctBy(또는 Distinct서명이 고유하기 때문에).
Tomas Aschan

1
나를 위해 작동하지 않습니다! < 'First'방법은 최종 쿼리 작업으로 만 사용할 수 있습니다. 대신이 인스턴스에서 'FirstOrDefault'메서드를 사용해보십시오.> 'FirstOrDefault'를 사용해도 작동하지 않습니다.
JatSing

63
@TorHaugen : 모든 그룹을 만드는 데 드는 비용이 있습니다. 이것은 입력을 스트리밍 할 수 없으며, 반환하기 전에 모든 데이터를 버퍼링합니다. 그것은 물론 당신의 상황과 관련이 없을 수도 있지만 DistinctBy의 우아함을 선호합니다 :)
Jon Skeet

2
@ JonSkeet : 이것은 하나의 기능을 위해 추가 라이브러리를 가져오고 싶지 않은 VB.NET 코더에게 충분합니다. ASync CTP가 없으면 VB.NET은 yield명령문을 지원하지 않으므로 기술적으로 스트리밍 할 수 없습니다. 그래도 답변 주셔서 감사합니다. C #으로 코딩 할 때 사용하겠습니다. ;-)
Alex Essilfie

2
@ BenGripka : 그것은 동일하지 않습니다. 고객 ID 만 제공합니다. 나는 전체 고객을 원한다 :)
ryanman

496

MoreLINQDistinctBy 에서 원하는 것처럼 보입니다 . 그런 다음 쓸 수 있습니다 :

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

다음은 컷 다운 버전입니다 DistinctBy(무효 검사 및 자체 키 비교기를 지정할 수있는 옵션 없음).

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

14
Jon Skeet이 게시물의 제목을 읽음으로써 최상의 답변이 게시 될 것이라는 것을 알고있었습니다. LINQ와 관련이 있다면 Skeet이 당신의 남자입니다. 신과 같은 linq 지식을 얻으려면 'C # In Depth'를 읽으십시오.
nocarrier

2
좋은 답변 !!! 또한 대한 모든 VB_Complainers을 위해 yield할 수있는 foreach는 + 추가 lib 디렉토리로 재 작성return source.Where(element => knownKeys.Add(keySelector(element)));
데니스 모로 조프

5
@ sudhAnsu63 이것은 LinqToSql (및 다른 linq 공급자)의 제한 사항입니다. LinqToX의 의도는 C # 람다 식을 X의 기본 컨텍스트로 변환하는 것입니다. 즉, LinqToSql은 C #을 SQL로 변환하고 가능한 경우 기본적으로 해당 명령을 실행합니다. 즉, SQL로 표현할 방법이없는 경우 (또는 사용중인 linq 공급자) C #에있는 메서드는 linqProvider를 "통과"할 수 없습니다. 확장 메소드에서 데이터 객체를 모델로 변환하는 것을 볼 수 있습니다. DistinctBy () 전에 ToList ()를 호출하여 쿼리를 "구체화"하여이 문제를 해결할 수 있습니다.
Michael Blackburn

1
그리고이 질문에 다시 올 때마다 나는 그들이 적어도 MoreLinq를 BCL에 채택하지 않는 이유를 계속 궁금해합니다.
Shimmy Weitzhandler

2
@ Shimmy : 나는 분명히 그것을 환영합니다 ... 타당성이 무엇인지 잘 모르겠습니다. 그래도 .NET Foundation에서 올릴 수 있습니다 ...
Jon Skeet

39

물건을 마무리합니다 . 나는 여기에 온 대부분의 사람들이 라이브러리를 사용하지 않고 가능한 최고의 성능으로 가능한 가장 간단한 솔루션을 원한다고 생각합니다 .

(나를 위해 방법으로 허용 된 그룹은 성능 측면에서 과잉이라고 생각합니다.)

다음은 null 값에도 작동 하는 IEqualityComparer 인터페이스를 사용하는 간단한 확장 방법 입니다.

용법:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

확장 방법 코드

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}

19

이와 같은 확장 방법 과부하는 없습니다. 나는 과거에 이것이 좌절감을 느꼈고 일반적 으로이 문제를 다루는 도우미 클래스를 작성합니다. 목표는를로 변환하는 Func<T,T,bool>IEqualityComparer<T,T>입니다.

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

이것은 당신이 다음을 쓸 수 있습니다

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

8
그래도 지저분한 해시 코드 구현이 있습니다. IEqualityComparer<T>프로젝션에서 보다 쉽게 ​​만들 수 있습니다 : stackoverflow.com/questions/188120/…
Jon Skeet

7
(해시 코드에 대한 내 의견을 설명하기 위해이 코드를 사용하면 Equals (x, y) == true이지만 GetHashCode (x)! = GetHashCode (y)로 끝나는 것이 매우 쉽습니다. 기본적으로 해시 테이블과 같은 것을 끊습니다. .)
Jon Skeet

해시 코드 이의 제기에 동의합니다. 여전히 패턴의 경우 +1입니다.
Tor Haugen

@ Jon, 그렇습니다 .GetHashcode의 원래 구현이 최적보다 적습니다 (게으름). 나는 본질적으로 이제 약간 더 표준적인 EqualityComparer <T> .Default.GetHashcode ()를 사용하도록 전환했습니다. 그러나이 시나리오에서 GetHashcode 구현이 작동하도록 보장 된 유일한 것은 단순히 상수 값을 반환하는 것입니다. 해시 테이블 조회를 종료하지만 기능적으로 올바른 것으로 보장됩니다.
JaredPar

1
@JaredPar : 맞습니다. 해시 코드는 사용중인 평등 함수와 일치해야합니다. 이것은 아마도 기본 이 아닙니다 . 그렇지 않으면 귀찮게하지 않을 것입니다.) 프로젝션을 선호하는 이유-평등과 합리적인 해시를 모두 얻을 수 있습니다 그런 식으로 코드를 작성하십시오. 또한 호출 코드의 중복이 줄어 듭니다. 분명히 그것은 당신이 동일한 투영을 두 번 원하는 경우에만 작동하지만, 실제로 본 모든 경우입니다 :)
Jon Skeet

18

속기 솔루션

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

1
이것이 왜 개선되는지에 대한 설명을 추가해 주시겠습니까?
Keith Pinson

Konrad 가하지 않았을 때 실제로 이것은 나에게 잘 작동했습니다.
neoscribe

13

이것은 당신이 원하는 것을 할 것이지만 성능에 대해서는 모르겠습니다.

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

적어도 장황하지 않습니다.


12

다음은 필요한 것을 수행하는 간단한 확장 방법입니다.

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

그들이 이와 같은 독특한 방법을 프레임 워크에 굽지 않은 것은 부끄러운 일입니다.


이것은 라이브러리를 추가하지 않고도 가장 좋은 솔루션입니다.
toddmo

그러나, 나는 x.Keyx.First()IEnumerable<T>
toddmo

@toddmo 피드백 주셔서 감사합니다 :-) 그래, 논리 소리 ... 추가 조사 후 답변을 업데이트합니다.
David Kirkland

1
간단하고 깨끗한 솔루션에 대한 감사의 말을 전하는 것이 결코 늦지 않았습니다
Ali

4

내가 잘 사용했던 것이 나를 위해 잘되었습니다.

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

@Mukus 왜 여기에 클래스 이름을 묻는 지 잘 모르겠습니다. IEqualityComparer를 구현하기 위해 클래스 이름을 지정해야 했으므로 My 접두사를 붙였습니다.
Kleinux

4

여기서 본 모든 솔루션은 이미 필적 할만한 필드를 선택하는 데 의존합니다. 그러나 다른 방식으로 비교 해야하는 경우이 솔루션 은 다음과 같은 경우 일반적으로 작동하는 것 같습니다.

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

LambdaComparer는 무엇이며 어디에서 가져 옵니까?
Patrick Graham

@PatrickGraham 답변에 연결 : brendan.enrick.com/post/…
Dmitry Ledentsov

3

다른 방법으로 :

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

시퀀스 리턴 고유 요소는 '_myCaustomerProperty'특성으로 비교합니다.


1
이것을 말하기 위해 여기에 왔습니다. 답변은 정답입니다.
Still.Tony

5
아니요, 원하는 것은 사용자 지정 속성의 고유 값이 아닌 한 허용되는 대답이 아닙니다. 일반적인 OP 질문은 객체 의 특정 속성을 기반으로 별개의 객체 를 반환하는 방법이었습니다 .
tomo

2

InlineComparer 를 사용할 수 있습니다

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

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

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

사용 샘플 :

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

출처 : https://stackoverflow.com/a/5969691/206730
Union에 IEqualityComparer 사용
명시 적 형식 비교기를 인라인으로 지정할 수 있습니까?


2

LambdaEqualityComparer를 사용할 수 있습니다.

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

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

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

        private readonly Func<T, T, bool> _equalsFunction;
    }

1

이를 수행하는 까다로운 방법 Aggregate()키 속성 값을 키로 사용하여 사전을 누산기로 사용하여 확장을 사용하는 것입니다 .

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

그리고 GroupBy 스타일 솔루션은 다음을 사용합니다 ToLookup().

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

멋지지만 Dictionary<int, Customer>대신 대신 만드는 것이 어떻습니까?
ruffin

0

IEnumerable이 있다고 가정하고 예제 델리게이트에서 c1과 c2 가이 목록의 두 가지 요소를 참조하고 싶습니까?

나는 당신이 자기 조인으로 이것을 달성 할 수 있다고 생각합니다 var distinctResults = from c1 in myList join c2 on myList


0

경우 Distinct()고유의 결과를 생성하지 않습니다,이 시도 :

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);


0

이를 수행하는 방법은 다음과 같습니다.

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

이 방법을 사용하면처럼 하나의 매개 변수를 지정하여 사용할 수 .MyDistinct(d => d.Name)있지만 다음과 같이 두 번째 매개 변수로 갖는 조건을 지정할 수도 있습니다.

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

NB 예를 들어 다른 기능도 지정할 수 있습니다 .LastOrDefault(...).


조건 만 노출하려면 다음과 같이 구현하여 훨씬 간단하게 만들 수 있습니다.

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

이 경우 쿼리는 다음과 같습니다.

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

NB 여기서는 표현이 더 간단하지만 참고 .MyDistinct2.FirstOrDefault(...)암시 적으로 사용합니다 .


참고 : 위 예제는 다음 데모 클래스를 사용합니다.

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};

0

IEnumerable 람다 확장 :

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

용법:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.