Sử dụng LINQ để nhận các mục trong một Danh sách <>, không có trong Danh sách khác <>


526

Tôi sẽ giả sử có một truy vấn LINQ đơn giản để làm điều này, tôi chỉ không chắc chắn chính xác làm thế nào.

Cho đoạn mã này:

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

Tôi muốn thực hiện một truy vấn LINQ để cung cấp cho tôi tất cả những người peopleList2không tham gia peopleList1.

Ví dụ này sẽ cho tôi hai người (ID = 4 & ID = 5)


3
Có lẽ đó là một ý tưởng tốt để làm cho ID chỉ đọc vì danh tính của một đối tượng không nên thay đổi theo thời gian tồn tại của nó. Tất nhiên trừ khi khung thử nghiệm hoặc ORM của bạn yêu cầu nó phải có thể thay đổi.
CodeInChaos

2
Chúng ta có thể gọi đây là "Trái (hoặc Phải) Không bao gồm Tham gia" theo sơ đồ này không?
Hạt đậu đỏ

Câu trả lời:


912

Điều này có thể được giải quyết bằng cách sử dụng biểu thức LINQ sau:

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

Một cách khác để thể hiện điều này thông qua LINQ, mà một số nhà phát triển thấy dễ đọc hơn:

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

Cảnh báo: Như đã lưu ý trong các nhận xét, các phương pháp này bắt buộc thao tác O (n * m) . Điều đó có thể tốt, nhưng có thể giới thiệu các vấn đề về hiệu suất và đặc biệt là nếu tập dữ liệu khá lớn. Nếu điều này không thỏa mãn yêu cầu về hiệu suất của bạn, bạn có thể cần phải đánh giá các tùy chọn khác. Do yêu cầu đã nêu là một giải pháp trong LINQ, tuy nhiên, các tùy chọn đó không được khám phá ở đây. Như mọi khi, hãy đánh giá mọi cách tiếp cận với các yêu cầu về hiệu suất mà dự án của bạn có thể có.


34
Bạn có biết rằng đó là giải pháp O (n * m) cho một vấn đề có thể dễ dàng giải quyết trong thời gian O (n + m) không?
Niki

32
@nikie, OP đã yêu cầu một giải pháp sử dụng Linq. Có lẽ anh ta đang cố gắng học Linq. Nếu câu hỏi là cách hiệu quả nhất, câu hỏi của tôi không nhất thiết phải giống như vậy.
Klaus Byskov Pedersen

46
@nikie, quan tâm chia sẻ giải pháp dễ dàng của bạn?
Rubio

18
Điều này là tương đương và tôi thấy dễ theo dõi hơn: var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = P.ID));
AntonK

28
@Menol - có thể hơi bất công khi chỉ trích ai đó trả lời đúng một câu hỏi. Mọi người không cần phải lường trước tất cả các cách và bối cảnh mà những người trong tương lai có thể vấp phải câu trả lời. Trong thực tế, bạn nên hướng điều đó đến nikie - người đã dành thời gian để nói rằng họ biết về một sự thay thế mà không cung cấp nó.
Chris Rogers

397

Nếu bạn ghi đè lên sự bình đẳng của Mọi người thì bạn cũng có thể sử dụng:

peopleList2.Except(peopleList1)

Exceptnên nhanh hơn đáng kể so với Where(...Any)biến thể vì nó có thể đưa danh sách thứ hai vào hashtable. Where(...Any)có thời gian chạy O(peopleList1.Count * peopleList2.Count)trong khi các biến thể dựa trên HashSet<T>(gần như) có thời gian chạy O(peopleList1.Count + peopleList2.Count).

Exceptngầm loại bỏ trùng lặp. Điều đó không ảnh hưởng đến trường hợp của bạn, nhưng có thể là một vấn đề cho các trường hợp tương tự.

Hoặc nếu bạn muốn mã nhanh nhưng không muốn ghi đè đẳng thức:

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

Biến thể này không loại bỏ trùng lặp.


Điều đó sẽ chỉ hoạt động nếu Equalsđã bị ghi đè để so sánh ID.
Klaus Byskov Pedersen

34
Đó là lý do tại sao tôi viết rằng bạn cần ghi đè lên sự bình đẳng. Nhưng tôi đã thêm một ví dụ hoạt động ngay cả khi không có điều đó.
CodeInChaos

4
Nó cũng sẽ hoạt động nếu Person là một cấu trúc. Mặc dù vậy, Person dường như là một lớp không hoàn chỉnh vì nó có một thuộc tính được gọi là "ID" không xác định được nó - nếu nó đã xác định nó, thì bằng sẽ bị ghi đè để ID bằng có nghĩa là Người bằng nhau. Khi lỗi đó trong Person đã được sửa, cách tiếp cận này sẽ tốt hơn (trừ khi lỗi được sửa bằng cách đổi tên "ID" thành một thứ khác không gây hiểu lầm bằng cách dường như là một định danh).
Jon Hanna

2
Nó cũng hoạt động rất tốt nếu bạn đang nói về một danh sách các chuỗi (hoặc các đối tượng cơ sở khác), đó là những gì tôi đang tìm kiếm khi tôi gặp chủ đề này.
Dan Korn

@DanKorn Tương tự, đây là một giải pháp đơn giản hơn, so với nơi, để so sánh cơ bản, int, đối tượng ref, chuỗi.
Mê cung

73

Hoặc nếu bạn muốn nó mà không phủ nhận:

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

Về cơ bản, nó nói lấy tất cả từ peopleList2 trong đó tất cả các id trong peopleList1 khác với id trong peoplesList2.

Chỉ cần một chút cách tiếp cận khác với câu trả lời được chấp nhận :)


5
Phương pháp này (danh sách hơn 50.000 mặt hàng) nhanh hơn đáng kể so với phương pháp BẤT K !!
DaveN

5
Điều này có thể nhanh hơn chỉ vì nó lười biếng. Lưu ý rằng điều này chưa làm bất kỳ công việc thực sự nào. Mãi cho đến khi bạn liệt kê danh sách mà nó thực sự hoạt động (bằng cách gọi ToList hoặc sử dụng nó như một phần của vòng lặp foreach, v.v.)
Xtros

32

Vì tất cả các giải pháp cho đến nay đều sử dụng cú pháp lưu loát, đây là một giải pháp trong cú pháp biểu thức truy vấn, dành cho những người quan tâm:

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

Tôi nghĩ rằng nó đủ khác với các câu trả lời được quan tâm cho một số người, thậm chí nghĩ rằng nó rất có thể sẽ là tối ưu cho Danh sách. Bây giờ đối với các bảng có ID được lập chỉ mục, đây chắc chắn sẽ là cách để đi.


Cảm ơn bạn. Câu trả lời đầu tiên làm phiền với cú pháp biểu thức truy vấn.
Tên chung

15

Đi dự tiệc muộn nhưng một giải pháp tốt cũng tương thích với Linq to SQL là:

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

Chuyển đến http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-Query-Joins-with-C


12

Câu trả lời của Klaus rất hay, nhưng ReSharper sẽ yêu cầu bạn "Đơn giản hóa biểu thức LINQ":

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


Cần lưu ý rằng thủ thuật này sẽ không hoạt động nếu có nhiều hơn một thuộc tính ràng buộc hai đối tượng (nghĩ rằng khóa tổng hợp SQL).
Alrekr

Alrekr - Nếu điều bạn muốn nói là "bạn sẽ cần so sánh nhiều thuộc tính hơn nếu nhiều thuộc tính cần so sánh hơn" thì tôi sẽ nói điều đó khá rõ ràng.
Lucas Morgan

8

Tiện ích mở rộng vô số này cho phép bạn xác định danh sách các mục cần loại trừ và chức năng sử dụng để tìm khóa sử dụng để thực hiện so sánh.

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

Bạn có thể sử dụng nó theo cách này

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

Bằng cách có mã mà @BrianT có, làm thế nào tôi có thể chuyển đổi nó để sử dụng mã của bạn?
Nicke Manarin

0

Dưới đây là một ví dụ hoạt động có được các kỹ năng CNTT mà ứng viên chưa có.

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

đầu tiên, trích xuất id từ bộ sưu tập trong điều kiện

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

thứ hai, sử dụng động từ "so sánh" để chọn id khác với lựa chọn

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

Rõ ràng bạn có thể sử dụng x.key! = "TEST", nhưng chỉ là một ví dụ


0

Khi bạn viết một FuncEqualityComparer chung, bạn có thể sử dụng nó ở mọi nơi.

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.