Gunakan LINQ untuk mendapatkan item dalam satu Daftar <>, yang tidak ada dalam Daftar lain <>

526

Saya akan berasumsi ada permintaan LINQ sederhana untuk melakukan ini, saya hanya tidak yakin bagaimana caranya.

Diberikan potongan kode ini:

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

Saya ingin melakukan kueri LINQ untuk memberi saya semua orang peopleList2yang tidak masuk peopleList1.

Contoh ini harus memberi saya dua orang (ID = 4 & ID = 5)

JSprang
sumber
3
Mungkin ide yang baik untuk membuat ID hanya dibaca karena identitas suatu objek tidak boleh berubah selama waktu hidup. Kecuali tentu saja kerangka pengujian atau ORM Anda mengharuskannya untuk bisa berubah.
CodesInChaos
2
Bisakah kita menyebutnya "Kiri (atau Kanan) Tidak Termasuk Gabung" menurut diagram ini?
The Red Pea

Jawaban:

912

Ini dapat diatasi menggunakan ekspresi LINQ berikut:

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

Cara alternatif untuk mengekspresikan ini melalui LINQ, yang menurut beberapa pengembang lebih mudah dibaca:

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

Peringatan: Seperti disebutkan dalam komentar, pendekatan ini mengamanatkan operasi O (n * m) . Itu mungkin baik-baik saja, tetapi bisa menimbulkan masalah kinerja, dan terutama jika kumpulan data cukup besar. Jika ini tidak memenuhi persyaratan kinerja Anda, Anda mungkin perlu mengevaluasi opsi lain. Karena persyaratan yang disebutkan adalah untuk solusi di LINQ, bagaimanapun, opsi tersebut tidak dieksplorasi di sini. Seperti biasa, evaluasi setiap pendekatan terhadap persyaratan kinerja yang mungkin dimiliki proyek Anda.

Klaus Byskov Pedersen
sumber
34
Anda sadar bahwa itu solusi O (n * m) untuk masalah yang dapat dengan mudah diselesaikan dalam waktu O (n + m)?
Niki
32
@nikie, OP meminta solusi yang menggunakan Linq. Mungkin dia mencoba mempelajari Linq. Jika pertanyaannya adalah untuk cara yang paling efisien, pertanyaan saya tidak harus sama.
Klaus Byskov Pedersen
46
@nikie, peduli untuk membagikan solusi mudah Anda?
Rubio
18
Ini sama dan saya menemukan lebih mudah untuk mengikuti: var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = P.ID));
AntonK
28
@Menol - mungkin agak tidak adil untuk mengkritik seseorang yang menanggapi pertanyaan dengan benar. Orang tidak perlu mengantisipasi semua cara dan konteks yang mungkin akan dijawab orang di masa depan. Pada kenyataannya, Anda harus mengarahkannya ke nikie - yang meluangkan waktu untuk menyatakan bahwa mereka tahu alternatif tanpa menyediakannya.
Chris Rogers
397

Jika Anda mengesampingkan kesetaraan Orang maka Anda juga dapat menggunakan:

peopleList2.Except(peopleList1)

Exceptharus secara signifikan lebih cepat daripada Where(...Any)varian karena dapat menempatkan daftar kedua ke dalam hashtable. Where(...Any)memiliki runtime O(peopleList1.Count * peopleList2.Count)sedangkan varian berdasarkan HashSet<T>(hampir) memiliki runtime O(peopleList1.Count + peopleList2.Count).

Exceptsecara implisit menghapus duplikat. Itu seharusnya tidak mempengaruhi kasus Anda, tetapi mungkin menjadi masalah untuk kasus serupa.

Atau jika Anda ingin kode cepat tetapi tidak ingin menimpa kesetaraan:

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

Varian ini tidak menghapus duplikat.

CodesInChaos
sumber
Itu hanya akan berfungsi jika Equalstelah diganti untuk membandingkan ID.
Klaus Byskov Pedersen
34
Itu sebabnya saya menulis bahwa Anda perlu menimpa kesetaraan. Tetapi saya telah menambahkan contoh yang berfungsi bahkan tanpa itu.
CodesInChaos
4
Ini juga akan berfungsi jika Orang adalah struct. Namun, Person tampaknya kelas yang tidak lengkap karena memiliki properti yang disebut "ID" yang tidak mengidentifikasinya - jika ia mengidentifikasi itu, maka persamaan akan ditimpa sehingga ID yang sama berarti Orang yang sama. Setelah bug di Person diperbaiki, pendekatan ini kemudian lebih baik (kecuali bug diperbaiki dengan mengganti nama "ID" ke sesuatu yang lain yang tidak menyesatkan dengan tampaknya menjadi pengidentifikasi).
Jon Hanna
2
Ini juga berfungsi dengan baik jika Anda berbicara tentang daftar string (atau objek dasar lainnya), yang merupakan apa yang saya cari ketika saya menemukan utas ini.
Dan Korn
@DanKorn Sama, ini solusi yang lebih sederhana, dibandingkan dengan di mana, untuk perbandingan dasar, int, objek ref, string.
Labirin
73

Atau jika Anda menginginkannya tanpa negasi:

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

Pada dasarnya dikatakan get all from peopleList2 di mana semua id di peopleList1 berbeda dari id di peopleList2.

Hanya sedikit pendekatan yang berbeda dari jawaban yang diterima :)

pengguna1271080
sumber
5
Metode ini (daftar lebih dari 50.000 item) secara signifikan lebih cepat daripada metode APAPUN!
DaveN
5
Ini mungkin lebih cepat hanya karena malas. Perhatikan bahwa ini belum melakukan pekerjaan nyata. Tidak sampai Anda menyebutkan daftar yang benar-benar berfungsi (dengan memanggil ToList atau menggunakannya sebagai bagian dari loop foreach, dll.)
Xtros
32

Karena semua solusi hingga saat ini menggunakan sintaks fasih, berikut adalah solusi dalam sintaks ekspresi kueri, untuk mereka yang tertarik:

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

Saya pikir itu cukup berbeda dari jawaban yang diberikan untuk menarik bagi beberapa orang, bahkan berpikir itu kemungkinan besar akan kurang optimal untuk Daftar. Sekarang untuk tabel dengan ID yang diindeks, ini pasti akan menjadi cara untuk pergi.

Michael Goldshteyn
sumber
Terima kasih. Jawaban pertama yang mengganggu sintaks ekspresi kueri.
Nama Umum
15

Agak terlambat ke pesta tetapi solusi bagus yang juga kompatibel dengan Linq untuk SQL adalah:

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

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

Richard Ockerby
sumber
12

Jawaban Klaus luar biasa, tetapi ReSharper akan meminta Anda untuk "Sederhanakan ekspresi LINQ":

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

Brian T
sumber
Patut dicatat bahwa trik ini tidak akan berfungsi jika ada lebih dari satu properti yang mengikat kedua objek (pikirkan kunci komposit SQL).
Alrekr
Alrekr - Jika apa yang ingin Anda katakan adalah "Anda perlu membandingkan lebih banyak properti jika lebih banyak properti perlu membandingkan" maka saya akan mengatakan itu cukup jelas.
Lucas Morgan
8

Ekstensi Enumerable ini memungkinkan Anda untuk menentukan daftar item untuk dikecualikan dan fungsi yang akan digunakan untuk menemukan kunci yang digunakan untuk melakukan perbandingan.

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

Anda bisa menggunakannya dengan cara ini

list1.Exclude(list2, i => i.ID);
Bertrand
sumber
Dengan memiliki kode yang dimiliki @BrianT, bagaimana saya bisa mengubahnya untuk menggunakan kode Anda?
Nicke Manarin
0

Berikut ini adalah contoh kerja yang mendapatkan keterampilan TI yang belum dimiliki oleh seorang calon pekerja.

//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);
Brian Quinn
sumber
0

pertama, ekstrak id dari koleksi di mana kondisinya

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

kedua, gunakan "bandingkan" untuk memilih id berbeda dengan seleksi

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

Jelas Anda dapat menggunakan x.key! = "TEST", tetapi hanya sebuah contoh

Ibngel Ibáñez
sumber
0

Setelah Anda menulis FuncEqualityComparer generik Anda dapat menggunakannya di mana saja.

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