Bir Listedeki <>, başka bir Listedeki <> olmayan öğeleri almak için LINQ kullanın


526

Bunu yapmak için basit bir LINQ sorgusu varsayalım, tam olarak nasıl emin değilim.

Bu kod parçası verildiğinde:

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

Bana bütün halk vermek için bir LINQ sorgusu yapmak istiyorum peopleList2o değildir peopleList1.

Bu örnek bana iki kişi vermelidir (ID = 4 & ID = 5)


3
Bir nesnenin kimliği canlı süresi boyunca değişmemesi gerektiğinden, kimliğini salt okunur yapmak iyi bir fikir olabilir. Elbette testiniz veya ORM çerçeveniz değiştirilebilir olmasını gerektirmedikçe.
CodesInChaos

Yanıtlar:


912

Bu, aşağıdaki LINQ ifadesi kullanılarak ele alınabilir:

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

Bunu, bazı geliştiricilerin daha okunabilir bulduğu LINQ ile ifade etmenin alternatif bir yolu:

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

Uyarı: Yorumlarda belirtildiği gibi, bu yaklaşımlar O (n * m) operasyonunu zorunlu kılar . Bu iyi olabilir, ancak özellikle de veri seti oldukça büyükse performans sorunları ortaya çıkarabilir. Bu, performans gereksinimlerinizi karşılamıyorsa, diğer seçenekleri değerlendirmeniz gerekebilir. Bununla birlikte, belirtilen gereksinim LINQ'da bir çözüm için olduğundan, bu seçenekler burada araştırılmamaktadır. Her zaman olduğu gibi, projenizin sahip olabileceği performans gereksinimlerine karşı herhangi bir yaklaşımı değerlendirin.


34
O (n * m) zamanında kolayca çözülebilecek bir probleme yönelik O (n * m) çözümü olduğunun farkında mısınız?
Niki

32
@nikie, OP Linq kullanan bir çözüm istedi. Belki de Linq'i öğrenmeye çalışıyor. Eğer soru en verimli şekilde olsaydı, sorum her zaman aynı olmayacaktı.
Klaus Byskov Pedersen

46
@nikie, kolay çözümünüzü paylaşmak ister misiniz?
Rubio

18
Bu eşdeğerdir ve takip etmeyi daha kolay buluyorum: var sonuç = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = P.ID));
AntonK

28
@Menol - bir soruya doğru cevap veren birini eleştirmek biraz haksız olabilir. İnsanların, gelecekteki insanların cevabına rastlayabilecekleri tüm yolları ve bağlamları tahmin etmeleri gerekmemelidir. Gerçekte, bunu sağlamadan bir alternatif bildiğini belirtmek için zaman alan nikie'ye yönlendirmelisiniz.
Chris Rogers

396

İnsanların eşitliğini geçersiz kılarsanız, şunları da kullanabilirsiniz:

peopleList2.Except(peopleList1)

ExceptWhere(...Any)varyanttan önemli ölçüde daha hızlı olmalıdır, çünkü ikinci listeyi hashtable içine koyabilir. Where(...Any)bir çalışma zamanını sahiptir O(peopleList1.Count * peopleList2.Count)dayanan varyantlar ise HashSet<T>(neredeyse) bir çalışma zamanını var O(peopleList1.Count + peopleList2.Count).

Exceptkopyaları dolaylı olarak kaldırır. Bu, davanızı etkilememelidir, ancak benzer davalar için bir sorun olabilir.

Veya hızlı kod istiyorsanız, ancak eşitliği geçersiz kılmak istemiyorsanız:

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

Bu varyant kopyaları kaldırmaz.


Bu, yalnızca Equalskimliklerini karşılaştırmak için geçersiz kılınmış olsaydı işe yarar .
Klaus Byskov Pedersen

34
Bu yüzden eşitliği geçersiz kılmanız gerektiğini yazdım. Ama onsuz da çalışan bir örnek ekledim.
CodesInChaos

4
Kişi bir yapı olsaydı da işe yarardı. Yine de, Kişi, onu tanımlayamayan "ID" adlı bir özelliğe sahip olduğu için eksik bir sınıf gibi görünüyor - eğer onu tanımlasaydı, eşit kimlik geçersiz kılınacak, böylece eşit kimlik eşit Kişi anlamına gelecektir. Kişi'deki bu hata giderildikten sonra, bu yaklaşım daha iyidir (hata, tanımlayıcı gibi görünerek yanıltıcı olmayan başka bir şeye "ID" yeniden adlandırılarak düzeltilmezse).
Jon Hanna

2
Ayrıca, bu konuya geldiğimde aradığım şey olan dizelerin (veya diğer temel nesnelerin) bir listesinden bahsediyorsanız da harika çalışır.
Dan Korn

@DanKorn Aynı şekilde, temel karşılaştırma için int, object ref, string'lerle karşılaştırıldığında bu daha basit bir çözüm.
Labirent

73

Veya olumsuzlama olmadan istiyorsanız:

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

Temel olarak, peopleList1 içindeki tüm kimliklerin peoplesList2'deki id'den farklı olduğu peopleList2 öğesinden alın diyor.

Kabul edilen cevaptan biraz farklı bir yaklaşım :)


5
Bu yöntem (50.000'den fazla öğenin listesi) HERHANGİ bir yöntemden önemli ölçüde daha hızlıydı!
DaveN

5
Tembel olduğu için bu daha hızlı olabilir. Bunun henüz gerçek bir iş yapmadığını unutmayın. İşi gerçekte yaptığı listeyi numaralandırıncaya kadar değil (
ToList'i

32

Bugüne kadarki tüm çözümler akıcı sözdizimini kullandığından, ilgilenenler için sorgu ifadesi sözdiziminde bir çözüm:

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

Bazıları ilgilendirmek için verilen cevaplardan yeterince farklı olduğunu düşünüyorum, hatta Listeler için en uygun olacağını düşündüm. Şimdi endeksli kimliğe sahip tablolar için, kesinlikle bu yol olacaktır.


Teşekkür ederim. Sorgu ifadesi sözdizimini rahatsız eden ilk yanıt.
Genel Ad

15

Partiye biraz geç ama aynı zamanda Linq to SQL uyumlu iyi bir çözüm:

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

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


12

Klaus'un cevabı harikaydı, ama ReSharper sizden "LINQ ifadesini basitleştirmenizi" isteyecektir:

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


İki nesneyi bağlayan birden fazla özellik varsa bu hile çalışmaz (SQL kompozit anahtarı düşünün).
Alrekr

Alrekr - Söylemek istediğiniz şey "daha fazla özellik karşılaştırmak gerekirse daha fazla özellik karşılaştırmak gerekir" ise, o zaman bu oldukça açık olduğunu söyleyebilirim.
Lucas Morgan

8

Bu Numaralandırılabilir Uzantı, hariç tutulacak öğelerin listesini ve karşılaştırma yapmak için kullanılacak anahtarı bulmak için kullanılacak bir işlevi tanımlamanıza olanak tanır.

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

Bu şekilde kullanabilirsiniz

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

@BrianT sahip kodu alarak, kodunuzu kullanmak için nasıl dönüştürebilirim?
Nicke Manarin

0

İşte bir iş adayının sahip olmadığı BT becerilerini elde eden çalışan bir örnek.

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

ilk olarak, koşulların bulunduğu koleksiyondaki kimlikleri çıkarın

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

ikinci olarak, seçime bağlı kimlikleri seçmek için "karşılaştır" estamentini kullanın

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

Açıkçası x.key! = "TEST" kullanabilirsiniz, ancak sadece bir örnektir


0

Genel bir FuncEqualityComparer yazdıktan sonra her yerde kullanabilirsiniz.

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.