답변:
일반적으로 @Sam에 대한 답변에 댓글을 달아서이 문제를 해결했습니다 (원래 게시물을 수정하여 동작을 변경하지 않고 조금 정리했습니다).
다음은 기본 해싱 정책에 대한 [IMNSHO] 중요 수정과 함께 @ Sam 's answer의 리프입니다 :-
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 ) 대신 최악의 O (N 2 ) 알고리즘을 사용하게됩니다!
다행히도 그렇지 않습니다. 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
커스텀 평등 비교기 를 사용하기 위한 완벽한 유스 케이스입니다 . Aku의 답변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>
구현에 적절한 것을 포함시키는 것이 왜 중요한지 분명히 알 수 있습니다.
orip의 답변 확대 :
여기에는 몇 가지 개선 사항이 있습니다.
Func<T, TKey>
대신에 Func<T, object>
; 이렇게하면 실제 keyExtractor
자체 에 값 유형 키가 박싱되지 않습니다.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));
}
}
StrictKeyEqualityComparer.Equals
방법은와 같습니다 KeyEqualityComparer.Equals
. 합니까 TKey : IEquatable<TKey>
제약 메이크업의 TKey.Equals
작업은 다르게?
TKey
임의의 유형일 수 있기 때문에 컴파일러는 가상적인 방법 Object.Equals
을 사용합니다 int
. 이후 후자의 경우, 그러나, TKey
구현하기 위해 구속 IEquatable<TKey>
의 TKey.Equals
방법은 어떤 권투를 필요로하지 것이다 사용됩니다.
StringKeyEqualityComparer<T, TKey>
입니다.
등식 검사를 사용자 정의하려는 경우 비교 자체가 아니라 비교할 키를 정의하는 데 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));
private readonly Func<T, int> _hashCodeResolver
에는 생성자에 전달되어 GetHashCode(...)
메소드에 사용되어야하는 다른 멤버 가 필요 합니다 .
obj.ToString().ToLower().GetHashCode()
대신에 사용 하고 obj.GetHashCode()
있습니까?
IEqualityComparer<T>
항상 배후에서 해싱을 사용하는 (예 : LINQ의 GroupBy, Distinct, Except, Join 등) 해시와 관련된 MS 계약이 중단됩니다. MS의 문서 발췌문은 다음과 같습니다. "두 객체 x와 y에 대해 Equals 메서드가 true를 반환하면 x에 대한 GetHashCode 메서드가 반환 한 값이 y에 대해 반환 된 값과 같아야합니다." 참조 : msdn.microsoft.com/ko-kr/library/ms132155
Dan Tao의 답변과 동일하지만 몇 가지 개선 사항이 있습니다.
에 의존는 EqualityComparer<>.Default
이 값 유형 (대한 복싱 피할 수 있도록 비교 실제 작업을 수행하기 위해 struct
시행하고 있습니다들) IEquatable<>
.
때문에 EqualityComparer<>.Default
사용이 폭발하지 않습니다 null.Equals(something)
.
비교기 IEqualityComparer<>
의 인스턴스를 작성하는 정적 메소드가있는 정적 랩퍼를 제공 하여 호출을 용이하게합니다. 비교
Equality<Person>.CreateComparer(p => p.ID);
와
new EqualityComparer<Person, int>(p => p.ID);
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);
사람은 간단한 수업입니다.
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 ) );
}
}
람다 식으로 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의 대답 은 훌륭합니다. 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();
}
}
참고 : 실제로 컴파일하고 아직 실행하지 않았으므로 오타 또는 다른 버그가있을 수 있습니다.
IEqualityComparer<T>
잎 것을GetHashCode
알아 단지 스트레이트 헤어 졌있다.