IEqualityComparer でデリゲートをラップする
質問
いくつかの Linq.Enumerable 関数は IEqualityComparer<T>
. 。を適応させる便利なラッパークラスはありますか delegate(T,T)=>bool
実装する IEqualityComparer<T>
?(正しいハッシュコードを定義する際の問題を無視すれば) 書くのは簡単ですが、すぐに使える解決策があるかどうか知りたいです。
具体的には、集合演算を行いたいのですが、 Dictionary
■ キーのみを使用してメンバーシップを定義します (さまざまなルールに従って値を保持します)。
解決
通常、この問題は、回答に @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(N2) 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
プロパティを使用すると、それらはすべて、によって返される 1 つの値にまとめられる必要があります。 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())
私自身の質問に答えます。Dictionaries をセットとして扱うには、セット操作を dict.Keys に適用し、Enumerable.ToDictionary(...) で Dictionaries に変換し直すのが最も簡単な方法のようです。
(ドイツ語テキスト) での実装 ラムダ式による 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));
}
}
最適化を 1 つだけ行います。値の比較には、委任するのではなく、すぐに使える 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#は2つの一般的な値を比較します
スシル
オリップの答え 素晴らしいです。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();
}
}
注記:まだ実際にコンパイルして実行していないので、タイプミスやその他のバグがある可能性があります。