128

いくつかのLinq.Enumerable関数は。を取りますIEqualityComparer<T>delegate(T,T)=>boolを実装するように適応させる便利なラッパークラスはありIEqualityComparer<T>ますか?(正しいハッシュコードの定義に関する問題を無視する場合)それを書くのは簡単ですが、すぐに使える解決策があるかどうか知りたいです。

Dictionary具体的には、キーのみを使用してメンバーシップを定義し(さまざまなルールに従って値を保持しながら)、sに対してセット操作を実行したいと思います。

4

13 に答える 13

174

の重要性についてGetHashCode

他の人は、カスタムIEqualityComparer<T>実装には実際にGetHashCodeメソッドを含める必要があるという事実についてすでにコメントしています。しかし、その理由を詳しく説明する人は誰もいません。

理由は次のとおりです。あなたの質問では、LINQ 拡張メソッドについて具体的に言及しています。これらのほとんどすべては、効率のために内部でハッシュ テーブルを使用するため、適切に機能するためにハッシュ コードに依存しています。

たとえばDistinct、使用されるすべてがメソッドである場合、この拡張メソッドの意味を検討してくださいEquals。しか持っていない場合、アイテムがシーケンスで既にスキャンされているかどうかをどのように判断しますかEquals? 既に調べた値のコレクション全体を列挙し、一致するかどうかを確認します。これにより、 O(N) アルゴリズムではなくDistinct、最悪の O(N 2 ) アルゴリズムが使用されることになります。

幸いなことに、そうではありません。を使用するだけでDistinctはありません。それも使います。実際、適切な を提供する がないと、正しく動作しません。以下は、これを説明する不自然な例です。EqualsGetHashCodeIEqualityComparer<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、それらはすべて によって返される 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);
}

出力:

×:1346013431
×:1388845717
×:1576754134
×:1104067189
×:1144789201
×:1862076501
×:1573781440
×: 646797592
×: 655632802
×: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);
    }
}

出力:

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

繰り返しますが、うまくいきませんでした。

考えてみれば、 がDistincta HashSet<T>(または同等のもの) を内部で使用し、 forが内部でGroupBya のようなものを使用するのは理にかなっていDictionary<TKey, List<T>>ます。これで、これらの方法が機能しない理由を説明できますか? これを試してみましょう:

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

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

出力:

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

ええ... 意味がわかり始めましたか?

GetHashCodeこれらの例から、IEqualityComparer<T>実装に適切なものを含めることが非常に重要である理由が明確になることを願っています。


元の答え

oripの答えを拡張する:

ここで行うことができる改善がいくつかあります。

  1. まず、;のFunc<T, TKey>代わりにa を使用します。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));
    }
}
于 2010-09-15T16:39:50.217 に答える
119

等値チェックをカスタマイズする場合、99% の確率で、比較自体ではなく、比較するキーを定義することに関心があります。

これは洗練された解決策になる可能性があります (Python のlist sort methodからの概念)。

使用法:

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();
    }
}
于 2009-08-06T14:41:23.787 に答える
47

残念ながら、すぐに使えるそのようなラッパーはありません。ただし、作成するのは難しくありません。

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));
于 2008-09-18T23:52:01.247 に答える
46

通常、私は答えに@Samをコメントすることでこれを解決します(動作を変更せずに少しクリーンアップするために元の投稿を編集しました)。

以下は、 @ 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 );
    }
}
于 2010-09-15T16:13:49.633 に答える
24

Dan Taoの答えと同じですが、いくつかの改善があります:

  1. に依存しEqualityComparer<>.Defaultて実際の比較を行うため、struct実装されている値の型 (複数可) のボックス化が回避され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; }
}
于 2011-08-02T14:25:17.287 に答える
11
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 ) );
    }
}
于 2008-11-06T20:44:50.210 に答える
6

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())
于 2011-05-27T09:44:57.567 に答える
2

私は自分自身の質問に答えるつもりです。辞書をセットとして扱うには、セット操作を dict.Keys に適用してから、Enumerable.ToDictionary(...) で辞書に戻すのが最も簡単な方法のようです。

于 2008-09-19T00:09:28.773 に答える
2

(ドイツ語のテキスト)ラムダ式を使用した 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));
       }
   }
于 2013-06-29T11:26:55.483 に答える
1

最適化の 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つの一般的な値を比較します

スシル

于 2010-06-29T15:17:21.183 に答える
0
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);

于 2011-05-30T13:10:48.653 に答える
-2

既存のクラスはわかりませんが、次のようなものです:

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

注: まだ実際にコンパイルして実行していないため、タイプミスやその他のバグがある可能性があります。

于 2008-09-18T23:55:44.873 に答える