문제

여러 Linq.Enumerable 함수는 IEqualityComparer<T>.다음을 적용하는 편리한 래퍼 클래스가 있습니까? delegate(T,T)=>bool 구현 IEqualityComparer<T>?하나를 작성하는 것은 충분히 쉽지만(올바른 해시코드를 정의하는 데 따른 문제를 무시하는 경우), 즉시 사용 가능한 솔루션이 있는지 알고 싶습니다.

특히, 나는 설정 작업을 수행하고 싶습니다. Dictionarys, 키만 사용하여 멤버십을 정의합니다(다른 규칙에 따라 값을 유지하면서).

도움이 되었습니까?

해결책

일반적으로 답변에 @Sam을 댓글로 달면 이 문제가 해결됩니다(동작을 변경하지 않고 약간 정리하기 위해 원본 게시물을 일부 편집했습니다.)

다음은 내 리프입니다 @샘의 답변, 기본 해싱 정책에 대한 [IMNSHO] 중요한 수정:-

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

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

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

다른 팁

중요성에 대하여 GetHashCode

다른 사람들은 이미 어떤 관습이 있다는 사실에 대해 논평했습니다. IEqualityComparer<T> 구현 실제로 GetHashCode 방법;하지만 아무도 귀찮게 설명하지 않지 세부적으로.

이유는 다음과 같습니다.귀하의 질문에는 LINQ 확장 방법이 구체적으로 언급되어 있습니다.거의 모두 이들 중 효율성을 위해 내부적으로 해시 테이블을 활용하기 때문에 제대로 작동하려면 해시 코드에 의존합니다.

가져가다 Distinct, 예를 들어.활용된 모든 것이 다음과 같은 것이라면 이 확장 방법의 의미를 고려하십시오. Equals 방법.다음 항목만 있는 경우 항목이 이미 순서대로 스캔되었는지 여부를 어떻게 확인합니까? Equals?이미 살펴본 전체 값 모음을 열거하고 일치하는 항목을 확인합니다.이로 인해 Distinct 최악의 경우 O(N 사용2) 알고리즘 대신 O(N) 알고리즘!

다행히도 그렇지 않습니다. Distinct 그렇지 않다 단지 사용 Equals;그것은 사용한다 GetHashCode 또한.사실은, 그것은 절대적으로 하지 않습니다 없이 제대로 작동 IEqualityComparer<T> 적절한 것을 공급하는 GetHashCode.아래는 이를 설명하는 고안된 예입니다.

다음과 같은 유형이 있다고 가정해 보겠습니다.

class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

이제 내가 가지고 있다고 말하면 List<Value> 고유한 이름을 가진 모든 요소를 ​​찾고 싶습니다.이는 다음을 위한 완벽한 사용 사례입니다. Distinct 사용자 정의 동등 비교자를 사용합니다.그럼 Comparer<T> 수업 아쿠의 대답:

var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

이제 우리에게 무리가 있다면 Value 같은 요소 Name 속성은 모두 다음에서 반환된 하나의 값으로 축소되어야 합니다. Distinct, 오른쪽?어디 보자 ...

var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

산출:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

흠, 그건 효과가 없었죠, 그렇죠?

는 어때 GroupBy?시도해 봅시다:

var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

산출:

[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

다시:작동하지 않았습니다.

생각해보면 말이 될 것 같다. Distinct 사용하다 HashSet<T> (또는 이에 상응하는) 내부적으로 GroupBy 같은 것을 사용하려면 Dictionary<TKey, List<T>> 내부적으로.이러한 방법이 작동하지 않는 이유를 설명할 수 있습니까?이것을 시도해 봅시다:

var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

산출:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

응...이해가 되기 시작했나요?

이러한 예를 통해 왜 적절한 항목을 포함하는지 분명해졌기를 바랍니다. GetHashCode 어떤 경우에도 IEqualityComparer<T> 구현이 너무 중요해요.


원래 답변

확장 중 오리프의 대답:

여기서는 몇 가지 개선이 가능합니다.

  1. 먼저, 나는 Func<T, TKey> 대신에 Func<T, object>;이렇게 하면 실제 값 유형 키의 박싱을 방지할 수 있습니다. keyExtractor 그 자체.
  2. 둘째, 실제로 where TKey : IEquatable<TKey> 강제;이것은 복싱을 예방할 것입니다 Equals 부르다 (object.Equals 소요 object 매개변수;당신은 IEquatable<TKey> 구현 TKey 권투 없이 매개변수).분명히 이는 너무 심각한 제한을 초래할 수 있으므로 제약 조건 없이 기본 클래스를 만들고 제약 조건이 있는 파생 클래스를 만들 수 있습니다.

결과 코드는 다음과 같습니다.

public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    protected readonly Func<T, TKey> keyExtractor;

    public KeyEqualityComparer(Func<T, TKey> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public virtual bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

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

public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
    where TKey : IEquatable<TKey>
{
    public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
        : base(keyExtractor)
    { }

    public override bool Equals(T x, T y)
    {
        // This will use the overload that accepts a TKey parameter
        // instead of an object parameter.
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }
}

동일성 검사를 사용자 정의하려는 경우 99%의 시간은 비교 자체가 아니라 비교할 키를 정의하는 데 관심이 있습니다.

이것은 우아한 해결책이 될 수 있습니다(Python의 개념 목록 정렬 방법).

용법:

var foo = new List<string> { "abc", "de", "DE" };

// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

그만큼 KeyEqualityComparer 수업:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, object> keyExtractor;

    public KeyEqualityComparer(Func<T,object> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

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

그런 포장재가 기본적으로 제공되지 않는 것이 유감입니다.그러나 하나를 만드는 것은 어렵지 않습니다.

class Comparer<T>: IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;

    public Comparer(Func<T, T, bool> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");

        _comparer = comparer;
    }

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

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

...

Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));

Dan Tao의 답변과 동일하지만 몇 가지 개선 사항이 있습니다.

  1. 에 의존 EqualityComparer<>.Default 값 유형에 대한 박싱을 피하기 위해 실제 비교를 수행합니다(structs) 구현한 것 IEquatable<>.

  2. 부터 EqualityComparer<>.Default 사용하면 폭발하지 않습니다. null.Equals(something).

  3. 주변에 정적 래퍼 제공 IEqualityComparer<> 비교자 인스턴스를 생성하는 정적 메서드가 있으므로 호출이 쉬워집니다.비교하다

    Equality<Person>.CreateComparer(p => p.ID);
    

    ~와 함께

    new EqualityComparer<Person, int>(p => p.ID);
    
  4. 지정할 오버로드를 추가했습니다. IEqualityComparer<> 열쇠를 위해.

클래스:

public static class Equality<T>
{
    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
    {
        return CreateComparer(keySelector, null);
    }

    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, 
                                                         IEqualityComparer<V> comparer)
    {
        return new KeyEqualityComparer<V>(keySelector, comparer);
    }

    class KeyEqualityComparer<V> : IEqualityComparer<T>
    {
        readonly Func<T, V> keySelector;
        readonly IEqualityComparer<V> comparer;

        public KeyEqualityComparer(Func<T, V> keySelector, 
                                   IEqualityComparer<V> comparer)
        {
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            this.keySelector = keySelector;
            this.comparer = comparer ?? EqualityComparer<V>.Default;
        }

        public bool Equals(T x, T y)
        {
            return comparer.Equals(keySelector(x), keySelector(y));
        }

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

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

var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

Person은 간단한 클래스입니다.

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => t.GetHashCode())
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

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

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

확장 기능 포함 :-

public static class SequenceExtensions
{
    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
    }

    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
    }
}

orip의 답변은 훌륭합니다.

더 쉽게 만들 수 있는 약간의 확장 방법은 다음과 같습니다.

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object>    keyExtractor)
{
    return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())

나는 내 자신의 질문에 답할 것입니다.사전을 집합으로 처리하는 가장 간단한 방법은 dict.Keys에 집합 작업을 적용한 다음 Enumerable.ToDictionary(...)를 사용하여 사전으로 다시 변환하는 것 같습니다.

(독일어 텍스트)의 구현 람다 식을 사용하여 IEqualityCompare 구현null 값에 관심을 갖고 확장 메서드를 사용하여 IEqualityComparer를 생성합니다.

Linq 공용체에서 IEqualityComparer를 만들려면 다음을 작성하면 됩니다.

persons1.Union(persons2, person => person.LastName)

비교자:

public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
  Func<TSource, TComparable> _keyGetter;

  public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
  {
    _keyGetter = keyGetter;
  }

  public bool Equals(TSource x, TSource y)
  {
    if (x == null || y == null) return (x == null && y == null);
    return object.Equals(_keyGetter(x), _keyGetter(y));
  }

  public int GetHashCode(TSource obj)
  {
    if (obj == null) return int.MinValue;
    var k = _keyGetter(obj);
    if (k == null) return int.MaxValue;
    return k.GetHashCode();
  }
}

유형 추론을 지원하려면 확장 메서드도 추가해야 합니다.

public static class LambdaEqualityComparer
{
       // source1.Union(source2, lambda)
        public static IEnumerable<TSource> Union<TSource, TComparable>(
           this IEnumerable<TSource> source1, 
           IEnumerable<TSource> source2, 
            Func<TSource, TComparable> keySelector)
        {
            return source1.Union(source2, 
               new LambdaEqualityComparer<TSource, TComparable>(keySelector));
       }
   }

단 하나의 최적화:값 비교를 위해 위임하는 대신 즉시 사용 가능한 EqualityComparer를 사용할 수 있습니다.

실제 비교 논리가 이제 이미 오버로드되었을 수 있는 GetHashCode() 및 Equals()에 유지되므로 구현이 더욱 깔끔해집니다.

코드는 다음과 같습니다.

public class MyComparer<T> : IEqualityComparer<T> 
{ 
  public bool Equals(T x, T y) 
  { 
    return EqualityComparer<T>.Default.Equals(x, y); 
  } 

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

개체에 GetHashCode() 및 Equals() 메서드를 오버로드하는 것을 잊지 마십시오.

이 게시물이 나에게 도움이 되었습니다: C# 두 개의 일반 값을 비교합니다.

수실

오리프의 대답 중대하다.orip의 답변을 확장하면 다음과 같습니다.

솔루션의 핵심은 "익명 유형"을 전송하기 위해 "확장 방법"을 사용하는 것이라고 생각합니다.

    public static class Comparer 
    {
      public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
      {
        return new KeyEqualityComparer<T>(keyExtractor);
      }
    }

용법:

var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
  {
     Dictionary<TKey, TValue> result = null;
     ICollection collection = items as ICollection;
     if (collection != null)
        result = new Dictionary<TKey, TValue>(collection.Count);
     else
        result = new Dictionary<TKey, TValue>();
     foreach (TValue item in items)
        result[selector(item)] = item;
     return result;
  }

이렇게 하면 다음과 같이 람다가 있는 속성을 선택할 수 있습니다. .Select(y => y.Article).Distinct(x => x.ArticleID);

나는 기존 수업을 모르지만 다음과 같습니다.

public class MyComparer<T> : IEqualityComparer<T>
{
  private Func<T, T, bool> _compare;
  MyComparer(Func<T, T, bool> compare)
  {
    _compare = compare;
  }

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

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

메모:아직 실제로 컴파일하고 실행하지 않았으므로 오타나 기타 버그가 있을 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top