LINQを使用して、あるList <>にあるが、別のList <>にないアイテムを取得する


526

これを行う簡単なLINQクエリがあると思いますが、その方法が正確にわかりません。

このコードを考えると:

class Program
{
    static void Main(string[] args)
    {
        List<Person> peopleList1 = new List<Person>();
        peopleList1.Add(new Person() { ID = 1 });
        peopleList1.Add(new Person() { ID = 2 });
        peopleList1.Add(new Person() { ID = 3 });

        List<Person> peopleList2 = new List<Person>();
        peopleList2.Add(new Person() { ID = 1 });
        peopleList2.Add(new Person() { ID = 2 });
        peopleList2.Add(new Person() { ID = 3 });
        peopleList2.Add(new Person() { ID = 4 });
        peopleList2.Add(new Person() { ID = 5 });
    }
}

class Person
{
    public int ID { get; set; }
}

私は私の人々のすべて与えるためにLINQクエリを実行したいpeopleList2ことをしていませんpeopleList1

この例では、2人(ID = 4とID = 5)になるはずです。


3
おそらく、オブジェクトのIDはその存続期間を通じて変更されるべきではないため、IDを読み取り専用にすることをお勧めします。もちろん、テストまたはORMフレームワークで変更可能にする必要がある場合を除きます。
CodesInChaos

回答:


912

これは、次のLINQ式を使用して対処できます。

var result = peopleList2.Where(p => !peopleList1.Any(p2 => p2.ID == p.ID));

これをLINQを介して表現する別の方法。

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

警告:コメントで述べたように、これらのアプローチはO(n * m)操作を義務付けています。これは問題ないかもしれませんが、特にデータセットが非常に大きい場合は、パフォーマンスの問題が発生する可能性があります。これでパフォーマンス要件が満たされない場合は、他のオプションを評価する必要があります。記載されている要件はLINQのソリューションに対するものであるため、ここではそれらのオプションについては説明しません。いつものように、プロジェクトのパフォーマンス要件に対してアプローチを評価してください。


34
あなたはそれがO(n + m)時間で簡単に解決できる問題のO(n * m)ソリューションであることを知っていますか?
Niki

32
@ nikie、OPはLinqを使用するソリューションを求めました。たぶん彼はLinqを学ぼうとしているのかもしれません。質問が最も効率的な方法であった場合、私の質問は必ずしも同じではなかったでしょう。
クラウスビスコフペデルセン

46
@nikie、簡単な解決策を共有してくれませんか?
Rubio

18
これは同等であり、私は従う方が簡単だと思います:var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID!= p.ID));
AntonK 2016年

28
@Menol-質問に正しく応答する人を批判するのは少し不公平かもしれません。人々は、将来の人々が答えに出くわす可能性があるすべての方法とコンテキストを予測する必要はありません。実際には、あなたはそれをニキーに向けるべきです-ニキーは、彼らが代替品を提供することなく知っていると時間をかけて述べました。
Chris Rogers

397

Peopleの平等をオーバーライドする場合は、次のものも使用できます。

peopleList2.Except(peopleList1)

ExceptWhere(...Any)2番目のリストをハッシュテーブルに入れることができるため、バリアントよりも大幅に高速になるはずです。Where(...Any)のランタイムがありますがO(peopleList1.Count * peopleList2.Count)HashSet<T>(ほとんど)に基づくバリアントのランタイムはO(peopleList1.Count + peopleList2.Count)です。

Except暗黙的に重複を削除します。それはあなたのケースに影響を与えるべきではありませんが、同様のケースでは問題になるかもしれません。

または、高速なコードが必要だが、同等性をオーバーライドしたくない場合:

var excludedIDs = new HashSet<int>(peopleList1.Select(p => p.ID));
var result = peopleList2.Where(p => !excludedIDs.Contains(p.ID));

このバリアントは重複を削除しません。


EqualsIDを比較するためにオーバーライドされた場合にのみ機能します。
クラウスビスコフペデルセン

34
だから、私はあなたが平等を無効にする必要があると書いたのです。しかし、それがなくても機能する例を追加しました。
CodesInChaos 2010年

4
Personが構造体の場合も機能します。ただし、Personには「ID」と呼ばれるプロパティがあり、それを識別しないため、Personは不完全なクラスのように見えます。識別した場合、equalsはオーバーライドされ、等しいIDは等しいPersonを意味します。Personのバグが修正されると、このアプローチはより優れたものになります(「ID」の名前を識別子のように見せかけて誤解させない別の名前に変更することでバグを修正する場合を除く)。
Jon Hanna

2
また、文字列(または他の基本オブジェクト)のリストについて話している場合にもうまくいきます。これは、このスレッドに出会ったときに探していたものです。
Dan Korn

@DanKorn同じですが、これは、基本的な比較であるint、objects ref、stringsと比較して、より単純なソリューションです。
迷路

73

または、否定せずにそれを望む場合:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

基本的に、peopleList1のすべてのIDがpeoplesList2のIDとは異なる、peopleList2からすべて取得することを示しています。

受け入れられた答えとは少し異なるアプローチ:)


5
この方法(50,000項目を超えるリスト)は、ANY方法よりも大幅に高速でした。
DaveN 2017

5
怠惰だからといって、これはもっと速いかもしれません。これはまだ実際の作業を行っていないことに注意してください。リストを列挙するまでは、実際に機能しません(ToListを呼び出すか、foreachループの一部として使用するなど)
Xtros

32

これまでのすべてのソリューションは流暢な構文を使用していたので、興味のある方のために、クエリ式構文のソリューションを次に示します。

var peopleDifference = 
  from person2 in peopleList2
  where !(
      from person1 in peopleList1 
      select person1.ID
    ).Contains(person2.ID)
  select person2;

それは、リストにとって最適ではない可能性が高いと考えられていたとしても、一部の人にとって興味深いとされる回答とは十分に異なると思います。インデックス付きのIDを持つテーブルの場合、これは間違いなく進むべき道です。


ありがとうございました。最初の答えは、クエリ式の構文に悩まされることです。
一般名

15

パーティーには少し遅れますが、Linq to SQL互換でもある優れたソリューションは次のとおりです。

List<string> list1 = new List<string>() { "1", "2", "3" };
List<string> list2 = new List<string>() { "2", "4" };

List<string> inList1ButNotList2 = (from o in list1
                                   join p in list2 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inList2ButNotList1 = (from o in list2
                                   join p in list1 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inBoth = (from o in list1
                       join p in list2 on o equals p into t
                       from od in t.DefaultIfEmpty()
                       where od != null
                       select od).ToList<string>();

http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-Cへの称賛


12

クラウスの答えは素晴らしかったが、ReSharperは「LINQ式を単純化する」ように求めます。

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));


2つのオブジェクトをバインドするプロパティが複数ある場合(SQL複合キーを考えてみてください)、このトリックは機能しないことに注意してください。
Alrekr 2018年

Alrekr-「より多くのプロパティを比較する必要がある場合は、より多くのプロパティを比較する必要がある」と言っている場合、それはかなり明白です。
Lucas Morgan

8

このEnumerable Extensionを使用すると、除外する項目のリストと、比較の実行に使用するキーを見つけるために使用する関数を定義できます。

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Exclude<TSource, TKey>(this IEnumerable<TSource> source,
    IEnumerable<TSource> exclude, Func<TSource, TKey> keySelector)
    {
       var excludedSet = new HashSet<TKey>(exclude.Select(keySelector));
       return source.Where(item => !excludedSet.Contains(keySelector(item)));
    }
}

このように使えます

list1.Exclude(list2, i => i.ID);

@BrianTのコードを使用して、コードを使用するように変換するにはどうすればよいですか?
Nicke Manarin

0

これは、求職者がまだ持っていないITスキルを取得する実用的な例です。

//Get a list of skills from the Skill table
IEnumerable<Skill> skillenum = skillrepository.Skill;
//Get a list of skills the candidate has                   
IEnumerable<CandSkill> candskillenum = candskillrepository.CandSkill
       .Where(p => p.Candidate_ID == Candidate_ID);             
//Using the enum lists with LINQ filter out the skills not in the candidate skill list
IEnumerable<Skill> skillenumresult = skillenum.Where(p => !candskillenum.Any(p2 => p2.Skill_ID == p.Skill_ID));
//Assign the selectable list to a viewBag
ViewBag.SelSkills = new SelectList(skillenumresult, "Skill_ID", "Skill_Name", 1);

0

まず、コレクションの条件からIDを抽出します

List<int> indexes_Yes = this.Contenido.Where(x => x.key == 'TEST').Select(x => x.Id).ToList();

第二に、 "compare" estamentを使用して、選択とは異なるIDを選択します

List<int> indexes_No = this.Contenido.Where(x => !indexes_Yes.Contains(x.Id)).Select(x => x.Id).ToList();

明らかに、x.key!= "TEST"を使用できますが、これは単なる例です。


0

汎用的なFuncEqualityComparerを作成すると、どこでも使用できます。

peopleList2.Except(peopleList1, new FuncEqualityComparer<Person>((p, q) => p.ID == q.ID));

public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> comparer;
    private readonly Func<T, int> hash;

    public FuncEqualityComparer(Func<T, T, bool> comparer)
    {
        this.comparer = comparer;
        if (typeof(T).GetMethod(nameof(object.GetHashCode)).DeclaringType == typeof(object))
            hash = (_) => 0;
        else
            hash = t => t.GetHashCode(); 
    }

    public bool Equals(T x, T y) => comparer(x, y);
    public int GetHashCode(T obj) => hash(obj);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.