IEqualityComparer에 대리자를 래핑합니다.
문제
여러 Linq.Enumerable 함수는 IEqualityComparer<T>
.다음을 적용하는 편리한 래퍼 클래스가 있습니까? delegate(T,T)=>bool
구현 IEqualityComparer<T>
?하나를 작성하는 것은 충분히 쉽지만(올바른 해시코드를 정의하는 데 따른 문제를 무시하는 경우), 즉시 사용 가능한 솔루션이 있는지 알고 싶습니다.
특히, 나는 설정 작업을 수행하고 싶습니다. Dictionary
s, 키만 사용하여 멤버십을 정의합니다(다른 규칙에 따라 값을 유지하면서).
해결책
일반적으로 답변에 @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>
구현이 너무 중요해요.
원래 답변
확장 중 오리프의 대답:
여기서는 몇 가지 개선이 가능합니다.
- 먼저, 나는
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));
}
}
동일성 검사를 사용자 정의하려는 경우 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의 답변과 동일하지만 몇 가지 개선 사항이 있습니다.
에 의존
EqualityComparer<>.Default
값 유형에 대한 박싱을 피하기 위해 실제 비교를 수행합니다(struct
s) 구현한 것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);
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();
}
}
메모:아직 실제로 컴파일하고 실행하지 않았으므로 오타나 기타 버그가 있을 수 있습니다.