Verwenden Sie LINQ, um Elemente in einer Liste <> abzurufen, die sich nicht in einer anderen Liste <> befinden


526

Ich würde annehmen, dass es eine einfache LINQ-Abfrage gibt, ich bin mir einfach nicht ganz sicher, wie.

Angesichts dieses Codes:

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

Ich möchte eine LINQ-Abfrage durchführen, um mir alle Personen mitzuteilen peopleList2, die nicht anwesend sind peopleList1.

Dieses Beispiel sollte mir zwei Personen geben (ID = 4 & ID = 5)


3
Vielleicht ist es eine gute Idee, die ID schreibgeschützt zu machen, da sich die Identität eines Objekts während seiner Lebensdauer nicht ändern sollte. Es sei denn natürlich, Ihr Test- oder ORM-Framework erfordert, dass es veränderbar ist.
CodesInChaos

2
Könnten wir dies gemäß diesem Diagramm
Die rote Erbse

Antworten:


912

Dies kann mit dem folgenden LINQ-Ausdruck behoben werden:

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

Eine alternative Möglichkeit, dies über LINQ auszudrücken, die einige Entwickler besser lesbar finden:

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

Warnung: Wie in den Kommentaren erwähnt, erfordern diese Ansätze eine O (n * m) -Operation. Das mag in Ordnung sein, kann aber zu Leistungsproblemen führen, insbesondere wenn der Datensatz ziemlich groß ist. Wenn dies Ihren Leistungsanforderungen nicht entspricht, müssen Sie möglicherweise andere Optionen bewerten. Da die angegebene Anforderung eine Lösung in LINQ ist, werden diese Optionen hier jedoch nicht untersucht. Bewerten Sie wie immer jeden Ansatz anhand der Leistungsanforderungen, die Ihr Projekt möglicherweise hat.


34
Sie wissen, dass dies eine O (n * m) -Lösung für ein Problem ist, das leicht in O (n + m) -Zeit gelöst werden kann?
Niki

32
@nikie, das OP fragte nach einer Lösung, die Linq verwendet. Vielleicht versucht er Linq zu lernen. Wenn die Frage am effizientesten gewesen wäre, wäre meine Frage nicht unbedingt dieselbe gewesen.
Klaus Byskov Pedersen

46
@nikie, möchtest du deine einfache Lösung teilen?
Rubio

18
Dies ist äquivalent und ich finde es einfacher zu folgen: var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = P.ID));
AntonK

28
@Menol - es könnte etwas unfair sein, jemanden zu kritisieren, der auf eine Frage richtig antwortet. Die Menschen sollten nicht alle Wege und Kontexte vorhersehen müssen, über die zukünftige Menschen auf die Antwort stoßen könnten. In Wirklichkeit sollten Sie das an Nikie weiterleiten - der sich die Zeit genommen hat, um zu erklären, dass er von einer Alternative wusste, ohne sie bereitzustellen.
Chris Rogers

396

Wenn Sie die Gleichheit der Menschen überschreiben, können Sie auch Folgendes verwenden:

peopleList2.Except(peopleList1)

Exceptsollte deutlich schneller sein als die Where(...Any)Variante, da sie die zweite Liste in eine Hashtabelle einfügen kann. Where(...Any)hat eine Laufzeit von, O(peopleList1.Count * peopleList2.Count)während Varianten, die auf HashSet<T>(fast) basieren , eine Laufzeit von haben O(peopleList1.Count + peopleList2.Count).

ExceptEntfernt implizit Duplikate. Dies sollte sich nicht auf Ihren Fall auswirken, könnte jedoch in ähnlichen Fällen ein Problem darstellen.

Oder wenn Sie schnellen Code möchten, aber die Gleichheit nicht überschreiben möchten:

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

Diese Variante entfernt keine Duplikate.


Das würde nur funktionieren, wenn Equalses überschrieben worden wäre, um IDs zu vergleichen.
Klaus Byskov Pedersen

34
Deshalb habe ich geschrieben, dass Sie die Gleichheit außer Kraft setzen müssen. Aber ich habe ein Beispiel hinzugefügt, das auch ohne das funktioniert.
CodesInChaos

4
Es würde auch funktionieren, wenn Person eine Struktur wäre. Wie es jedoch ist, scheint Person eine unvollständige Klasse zu sein, da sie eine Eigenschaft namens "ID" hat, die sie nicht identifiziert. Wenn sie identifiziert wird, werden Gleiche gleich überschrieben, sodass gleiche ID gleiche Person bedeutet. Sobald dieser Fehler in Person behoben ist, ist dieser Ansatz besser (es sei denn, der Fehler wird behoben, indem "ID" in etwas anderes umbenannt wird, das nicht irreführend erscheint, indem es als Kennung erscheint).
Jon Hanna

2
Es funktioniert auch hervorragend, wenn Sie über eine Liste von Zeichenfolgen (oder anderen Basisobjekten) sprechen, nach denen ich gesucht habe, als ich auf diesen Thread gestoßen bin.
Dan Korn

@DanKorn Same, dies ist eine einfachere Lösung im Vergleich zu where, für den grundlegenden Vergleich int, object ref, strings.
Labyrinth

73

Oder wenn Sie es ohne Verneinung wollen:

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

Grundsätzlich heißt es, alle von peopleList2 abrufen, wobei sich alle IDs in peopleList1 von den IDs in peopleList2 unterscheiden.

Nur ein bisschen anders als die akzeptierte Antwort :)


5
Diese Methode (Liste von über 50.000 Artikeln) war deutlich schneller als die ANY-Methode!
DaveN

5
Dies könnte schneller sein, nur weil es faul ist. Beachten Sie, dass dies noch keine echte Arbeit leistet. Erst wenn Sie die Liste auflisten, erledigt sie die Arbeit tatsächlich (indem Sie ToList aufrufen oder als Teil einer foreach-Schleife usw. verwenden)
Xtros

32

Da alle bisherigen Lösungen eine flüssige Syntax verwendeten, finden Sie hier eine Lösung für die Syntax von Abfrageausdrücken für Interessierte:

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

Ich denke, es unterscheidet sich genug von den Antworten, die für einige von Interesse sind, obwohl ich dachte, dass es für Listen höchstwahrscheinlich nicht optimal wäre. Für Tabellen mit indizierten IDs wäre dies definitiv der richtige Weg.


Vielen Dank. Erste Antwort, die die Syntax des Abfrageausdrucks stört.
Generischer Name

15

Etwas spät zur Party, aber eine gute Lösung, die auch mit Linq to SQL kompatibel ist, ist:

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

Ein großes Lob an http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-C


12

Klaus 'Antwort war großartig, aber ReSharper wird Sie bitten, "den LINQ-Ausdruck zu vereinfachen":

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


Es ist zu beachten, dass dieser Trick nicht funktioniert, wenn mehr als eine Eigenschaft die beiden Objekte bindet (denken Sie an den zusammengesetzten SQL-Schlüssel).
Alrekr

Alrekr - Wenn Sie sagen wollen "Sie müssen mehr Eigenschaften vergleichen, wenn mehr Eigenschaften verglichen werden müssen", dann würde ich sagen, dass das ziemlich offensichtlich ist.
Lucas Morgan

8

Mit dieser Enumerable-Erweiterung können Sie eine Liste der auszuschließenden Elemente und eine Funktion zum Suchen des Schlüssels definieren, der zum Durchführen eines Vergleichs verwendet werden soll.

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

Sie können es auf diese Weise verwenden

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

Wie kann ich den Code von @BrianT konvertieren, um Ihren Code zu verwenden?
Nicke Manarin

0

Hier ist ein Arbeitsbeispiel, das IT-Kenntnisse vermittelt, über die ein Bewerber noch nicht verfügt.

//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

Extrahieren Sie zunächst IDs aus der Sammlung where-Bedingung

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

Zweitens: Verwenden Sie "Vergleichen", um IDs auszuwählen, die sich von der Auswahl unterscheiden

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

Natürlich können Sie x.key! = "TEST" verwenden, dies ist jedoch nur ein Beispiel


0

Sobald Sie einen generischen FuncEqualityComparer geschrieben haben, können Sie ihn überall verwenden.

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);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.