2010-10-15 14 views
338

Suppongo che ci sia una semplice query LINQ per fare questo, non sono esattamente sicuro di come. Si prega di vedere lo snippet di codice qui sotto.Usa LINQ per ottenere elementi in un elenco <>, che non sono in un altro elenco <>

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

Vorrei eseguire una query LINQ per darmi tutte le persone in peopleList2 che non sono in peopleList1 questo esempio dovrebbe darmi due persone (ID = 4 & ID = 5)

+2

Forse è una buona idea di rendere ID di sola lettura in quanto l'identità di un oggetto non dovrebbe cambiare nel suo tempo di vita. A meno che, naturalmente, il tuo testing o la struttura ORM non richieda che sia mutabile. – CodesInChaos

+0

Possiamo chiamare questo "unire escluso a sinistra (o destro)" in base a [questo diagramma?] (Https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins) –

risposta

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

@JSprang , bello, prego. Dovresti contrassegnare la mia risposta come * la risposta * se ti ha aiutato (facendo clic sul segno di spunta a sinistra), in questo modo gli altri possono vedere che questa era la risposta corretta (e ottengo più reputazione ;-) –

+26

Sei consapevole che questa è una soluzione O (n * m) per un problema che può essere facilmente risolto in tempo O (n + m)? – Niki

+0

Sì, non mi ha lasciato segnare come risposta subito, ho detto che dovevo aspettare 5 minuti :) Grazie ancora! – JSprang

260

Se si ignora l'uguaglianza delle persone allora si può anche utilizzare:

peopleList2.Except(peopleList1) 

Except dovrebbe essere significativamente più veloce la variante Where(...Any) poiché può mettere la seconda lista in una tabella hash. Where(...Any) ha un runtime di O(peopleList1.Count * peopleList2.Count) mentre le varianti basate su HashSet<T> (quasi) hanno un runtime di O(peopleList1.Count + peopleList2.Count).

Except rimuove implicitamente i duplicati. Ciò non dovrebbe influenzare il tuo caso, ma potrebbe essere un problema per casi simili.

Oppure, se volete il codice veloce, ma non si vuole ignorare l'uguaglianza:

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

questa variante non rimuovere i duplicati.

+0

Funzionerebbe solo se 'Equals' è stato sovrascritto per confrontare gli ID. –

+0

Corretto @klausbyskov, ho appena provato questo e ottengo 5 risultati. – JSprang

+16

Ecco perché ho scritto che è necessario scavalcare l'uguaglianza. Ma ho aggiunto un esempio che funziona anche senza quello. – CodesInChaos

28

Dal momento che tutte le soluzioni fino ad oggi utilizzati sintassi fluente, ecco una soluzione di sintassi delle espressioni di query, per chi fosse interessato:

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

penso che sia abbastanza diversa dalle risposte date ad interessare alcuni, anche pensato che molto probabilmente sarebbe subottimale per le liste. Ora per le tabelle con ID indicizzati, questa sarebbe sicuramente la strada da percorrere.

0

Ecco un esempio pratico che acquisisce le competenze IT che un candidato non ha già .

//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); 
36

O se si desidera senza negazione:

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

In sostanza si dice ottenere tutto da peopleList2 dove tutti gli ID di peopleList1 diversi da id in peoplesList2.

Solo un approccio diverso po 'dalla risposta accettata :)

+2

Questo metodo (elenco di oltre 50.000 articoli) era significativamente più veloce del metodo ANY! – DaveN

10

po' tardi per il partito, ma una buona soluzione, che è anche compatibile con 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>(); 

Complimenti a http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-C

4

Questa estensione enumerabile consente di definire un elenco di elementi da escludere e una funzione da utilizzare per trovare la chiave da utilizzare per eseguire il confronto.

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

Si può usare questo modo

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

risposta Klaus' stato grande, ma ReSharper vi chiederà di "Semplificare l'espressione LINQ":

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

Problemi correlati