2010-10-15 11 views
338

Założę się, że istnieje proste zapytanie LINQ, aby to zrobić, po prostu nie jestem do końca pewien, w jaki sposób. Zobacz poniższy fragment kodu.Użyj LINQ, aby pobrać elementy z jednej listy <>, które nie znajdują się na innej liście <>

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

chciałbym wykonać kwerendy LINQ, aby dać mi wszystkich ludzi w peopleList2, które nie są w peopleList1 ten przykład powinien dać mi dwie osoby (ID = 4 & ID = 5)

+2

Być może jest to dobry pomysł na stworzenie ID tylko do odczytu, ponieważ tożsamość obiektu nie powinna się zmieniać w czasie jego działania. O ile oczywiście struktura testowania lub ORM nie wymaga modyfikacji. – CodesInChaos

+0

Czy możemy to nazwać "lewą (lub prawą) wykluczającą dołączenie" zgodnie z [tym schematem?] (Https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins) –

Odpowiedz

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

@JSprang , cool, nie ma za co. Powinieneś oznaczyć moją odpowiedź jako * odpowiedź *, jeśli ci pomogła (klikając na znacznik wyboru po lewej), w ten sposób inni zobaczą, że to była poprawna odpowiedź (i mam więcej reputacji ;-)) –

+26

Jesteś świadomy, że to jest O (n * m) rozwiązanie problemu, który można łatwo rozwiązać w czasie O (n + m)? – Niki

+0

Tak, to nie pozwoliłoby mi oznaczyć go jako odpowiedź od razu, powiedziałem, że muszę poczekać 5 minut :) Jeszcze raz dziękuję! – JSprang

260

Jeśli zastąpić równość ludzi, to można również użyć:

peopleList2.Except(peopleList1) 

Except powinny być znacznie szybsze niż wariant Where(...Any), ponieważ może umieścić drugą listę w tablicy hashtable. Where(...Any) ma środowisko wykonawcze O(peopleList1.Count * peopleList2.Count), podczas gdy warianty oparte na HashSet<T> (prawie) mają środowisko wykonawcze O(peopleList1.Count + peopleList2.Count).

Except niejawnie usuwa duplikaty. Nie powinno to wpłynąć na twoją sprawę, ale może być problemem w podobnych przypadkach.

Albo jeśli chcesz szybki kod, ale nie chce, aby zastąpić równość:

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

Wariant ten nie usuwa duplikaty.

+0

To zadziała tylko wtedy, gdy 'Equals' zostanie nadpisany w celu porównania identyfikatorów. –

+0

Popraw to @klausbyskov, właśnie wypróbowałem to i dostaję 5 wyników. – JSprang

+16

Dlatego napisałem, że musisz zastąpić równość. Ale dodałem przykład, który działa nawet bez tego. – CodesInChaos

28

Ponieważ wszystkie rozwiązania do tej pory wykorzystywanych składnia biegle, o to rozwiązanie w składni wyrażeń zapytań, dla zainteresowanych:

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

myślę, że jest na tyle różni się od odpowiedzi udzielonych być przedmiotem zainteresowania niektórzy, nawet myśleli, że najprawdopodobniej byłoby to nieoptymalne dla List. Teraz w przypadku tabel z indeksowanymi identyfikatorami z pewnością byłaby to najlepsza droga.

0

Oto działający przykład, który pozwala uzyskać umiejętności informatyczne, których kandydat już nie ma.

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

Albo jeśli chcesz go bez negacji:

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

Zasadniczo mówi dostać wszystko od peopleList2 gdzie wszystkie identyfikatory w peopleList1 różnią się od id w peoplesList2.

Tylko trochę inne podejście od przyjętego odpowiedź :)

+2

Ta metoda (lista ponad 50 000 pozycji) była znacznie szybsza niż DOWOLNA metoda! – DaveN

10

trochę późno do partii, ale dobre rozwiązanie, które jest również LINQ do SQL kompatybilne jest:

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

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

4

To rozszerzalne rozszerzenie pozwala zdefiniować listę elementów do wykluczenia oraz funkcję, która posłuży do znalezienia klucza do użycia w celu porównania.

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

można go używać w ten sposób

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

Klausa odpowiedź była wielka, ale ReSharper poprosi o "Uprość wyrażenie LINQ":

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

Powiązane problemy