IT박스

람다와 구별 ()?

itboxs 2020. 9. 30. 09:01
반응형

람다와 구별 ()?


맞아요, 그래서 저는 열거 형을 가지고 있고 그것으로부터 별개의 값을 얻고 싶습니다.

를 사용하면 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를 인라인으로 지정하는 방법이 있습니까?

최신 정보

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

문제는 두 개체가 같을 때 동일한 GetHashCode 반환 값을 가져야한다는 것입니다 (그렇지 않으면 Distinct에서 내부적으로 사용하는 해시 테이블이 제대로 작동하지 않음). IEqualityComparer는 Equals 및 GetHashCode의 호환 가능한 구현을 단일 인터페이스로 패키징하기 때문에 사용합니다.

말이 되겠네요 ..


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

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

정리하려면 . 저처럼 여기에 온 대부분의 사람들은 라이브러리를 사용하지 않고 최상의 성능으로 가능한 가장 간단한 솔루션을 원한다고 생각합니다 .

(제 생각에 방법별로 허용되는 그룹은 성능 측면에서 과잉이라고 생각합니다.)

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

이에 대한 확장 메서드 오버로드는 없습니다. 나는 이것이 과거에 자신을 실망시키는 것을 발견했고, 따라서 나는 보통이 문제를 다루는 도우미 클래스를 작성한다. 목표는 변환하는 것입니다 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));

속기 솔루션

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

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

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

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


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

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

이런 독특한 방법을 프레임 워크에 적용하지 않은 것은 아쉽지만 헤이 호.


나에게 잘 작동하는 내가 사용한 것.

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

다른 방법을 사용하십시오.

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

시퀀스는 고유 한 요소를 반환하며 '_myCaustomerProperty'속성별로 비교합니다.


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

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

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

Source: https://stackoverflow.com/a/5969691/206730
Using IEqualityComparer for Union
Can I specify my explicit type comparator inline?


A tricky way to do this is use Aggregate() extension, using a dictionary as accumulator with the key-property values as keys:

var customers = new List<Customer>();

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

And a GroupBy-style solution is using ToLookup():

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

You can use 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;
    }

I'm assuming you have an IEnumerable, and in your example delegate, you would like c1 and c2 to be referring to two elements in this list?

I believe you could achieve this with a self join var distinctResults = from c1 in myList join c2 in myList on


If Distinct() doesn't produce unique results, try this one:

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

The Microsoft System.Interactive package has a version of Distinct that takes a key selector lambda. This is effectively the same as Jon Skeet's solution, but it may be helpful for people to know, and to check out the rest of the library.


Here's how you can do it:

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

This method allows you to use it by specifying one parameter like .MyDistinct(d => d.Name), but it also allows you to specify a having condition as a second parameter like so:

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

N.B. This would also allow you to specify other functions like for example .LastOrDefault(...) as well.


If you want to expose just the condition, you can have it even simpler by implementing it as:

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

In this case, the query would just look like:

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

N.B. Here, the expression is simpler, but note .MyDistinct2 uses .FirstOrDefault(...) implicitly.


Note: The examples above are using the following demo class

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

IEnumerable lambda extension:

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

Usage:

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

참고URL : https://stackoverflow.com/questions/1300088/distinct-with-lambda

반응형