オブジェクトのコレクションが 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()
プラス customIEqualityComparer<>
、次の行に沿って:
-
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()
ですか?