2

オブジェクトのコレクションが 2 つあるとします。2 番目のコレクションに含まれていない最初のコレクションのオブジェクトを取得したいと考えています。

プリミティブ型のコレクションの場合、それは簡単です:

new[]{1,2,3,4}.Except(new[]{2,3}); //  => {1, 4}

しかし、もっと複雑な構造を使用したい場合はどうすればよいでしょうか? 以下の例では、フィールドを使用して比較しIdます。

class Person { string Name; int Id ; }

var lst1 = new[]{ new Person("Ann", 1), new Person("Bob", 2) };
var lst2 = new[]{ new Person("Cathy", 3), new Person("Bob", 2) };

一般的なコンセンサスでは、次の 2 つのオプションが提供されているようです。

  • Enumerable.Except()プラス custom IEqualityComparer<>、次の行に沿って:

-

class IdComparer: IEqualityComparer<Person> { /* boilerplate Equals(), GetHashCode() */ }

lst1.Except(lst2, new IdComparer())
    .Select(p=>p.Name);              // => { "Ann" } 

この方法は、等値基準を定義するのが面倒です。

  • 否定を使用.Contains()- まだIEqualityComparer<>;が必要です。または否定.Any()- これにより、条件をインラインで指定できます。

-

from p1 in lst1
where ! lst2.Any(p2 => p1.Id == p2.Id)
select p1.Name;                      // => { "Ann" } 

これは使いやすいですが、複雑さ O(M*N) のように見える「lst1 の各要素について、lst2 の各要素をチェックする」のように読みます。異なる Linq プロバイダーがこれを最適化 (できる) かどうかはわかりません。

複雑さに関しては、この.Except()方法の方がかなりうまくいきます:を使用するためSet<>、おおよそ O(M+N)です。

  • Sql の 'left-outer-join-filtered-by-NULLs' トリックはどうですか? これへの参照が見つからなかったので、十分に検索しなかったか、欠陥があります。

-

from p1 in lst1
join p2 in lst2 on p1.Id equals p2.Id into grp
where ! grp.Any()
select p1.Name;                     // => { "Ann" }

これにより、フィールドを使用して簡単に比較できます。
また、私が知る限り (Enumerable.JoinIterator()実装を掘り下げて)、複雑さはまだおおよそ O(M+N) です。

これは の良い代替品Enumerable.Except()ですか?

4

2 に答える 2

4

ExceptBymoreLINQ ライブラリの拡張メソッドを使用できます

比較に使用するキーを指定できます。

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector)

または等値比較子を指定することもできます:

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer)
于 2013-06-18T12:01:57.737 に答える
0

例外を使用して解決策があります。

これを見てください:

public class PropertyEqualityComparer<TObject, TProperty> 
    : IEqualityComparer<TObject>
{
    Func<TObject, TProperty> _selector;
    IEqualityComparer<TProperty> _internalComparer;
    public PropertyEqualityComparer(Func<TObject, TProperty> propertySelector,
        IEqualityComparer<TProperty> innerEqualityComparer = null)
    {
        _selector = propertySelector;
        _internalComparer = innerEqualityComparer;
    }
    public int GetHashCode(TObject obj)
    {
        return _selector(obj).GetHashCode();
    }
    public bool Equals(TObject x, TObject y)
    {
        IEqualityComparer<TProperty> comparer = 
            _internalComparer ?? EqualityComparer<TProperty>.Default;
        return comparer.Equals(_selector(x), _selector(y));
    }
}
public static class PropertyEqualityComparer
{
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>(Func<TObject, TProperty> propertySelector)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector);
    }
    public static PropertyEqualityComparer<TObject, TProperty>
        GetNew<TObject, TProperty>
        (Func<TObject, TProperty> propertySelector, 
        IEqualityComparer<TProperty> comparer)
    { 
        return new PropertyEqualityComparer<TObject, TProperty>
            (propertySelector, comparer);
    }
}

基本的には、セレクターを使用して比較できる IEqualityComparer を使用できるようにすることです。その後、次のように使用できます。

lst1.Except(lst2, PropertyEqualityComparer.GetNew(n => n.Id));

(コードのフォーマットについては、モバイルで申し訳ありません。)

于 2013-06-18T12:04:22.817 に答える