Sự khác biệt giữa hai danh sách


118

Tôi có hai danh sách chung chứa đầy đủ các đối tượng CustomsObject.

Tôi cần truy xuất sự khác biệt giữa hai danh sách đó (Các mục nằm trong danh sách đầu tiên mà không có các mục trong danh sách thứ hai) trong danh sách thứ ba.

Tôi đã nghĩ việc sử dụng .Except()là một ý tưởng hay nhưng tôi không biết cách sử dụng cái này .. Help!

c# 

Câu trả lời:


237

Sử dụng Exceptchính xác là cách đúng đắn để đi. Nếu kiểu của bạn ghi đè EqualsGetHashCodehoặc bạn chỉ quan tâm đến bình đẳng kiểu tham chiếu (tức là hai tham chiếu chỉ "bằng nhau" nếu chúng tham chiếu đến cùng một đối tượng), bạn chỉ có thể sử dụng:

var list3 = list1.Except(list2).ToList();

Nếu bạn cần thể hiện một ý tưởng tùy chỉnh về bình đẳng, ví dụ như bằng ID, bạn sẽ cần phải thực hiện IEqualityComparer<T>. Ví dụ:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Sau đó sử dụng:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Lưu ý rằng điều này sẽ loại bỏ bất kỳ phần tử trùng lặp nào. Nếu bạn cần giữ lại các bản sao, có lẽ sẽ dễ dàng nhất để tạo một tập hợp từ list2và sử dụng những thứ như:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

18
Ngoài ra, tôi muốn thêm đó Exceptlà hoạt động Đặt , sau đó danh sách kết quả sẽ có các giá trị riêng biệt, ví dụ: {'A','A','B','C'}.Except({'B','C'}) trả về{'A'}
digEmAll

4
Tôi có cái này: {'A', 'A', 'B', 'C'}. Ngoại trừ ({'B', 'C'}) trả về {'A'} hoạt động ... tuy nhiên {'B' , 'C'}. Ngoại trừ ({'A', 'B', 'C'}) KHÔNG trả lại {'A'}
MrLister

2
Sự khác biệt tập hợp của hai tập hợp được xác định là các thành viên của tập hợp đầu tiên không xuất hiện trong tập hợp thứ hai (trích dẫn MS). Vì vậy, {B, C} .Except ({A, B, C}) sẽ không trả về gì vì B và C đều nằm trong tập thứ hai. Không phải là một lỗi, nhiều hơn nữa liên quan đến định nghĩa toán học của hàm.
Steve Hibbert

Vậy @JonSkeet nếu tôi muốn so sánh hai danh sách trên cơ sở giả sử 2 thuộc tính, tôi có thể viết theo cách này so sánh và nhận được các mục không bằng nhau phải không?
Ehsan Sajjad

1
@NetMage: OP nói rằng họ muốn "Các mục có trong đầu tiên mà không có các mục trong thứ hai" - điều đó nghe có vẻ như là một sự khác biệt đối với tôi. Nếu danh sách đầu tiên chứa {5, 5, 5, 5, 1} và danh sách thứ hai chứa {5} thì chỉ có 1 trong danh sách đầu tiên chứ không phải danh sách thứ hai.
Jon Skeet

72

Bạn có thể làm điều gì đó như sau:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

nó đã tiết kiệm cho tôi một vòng lặp foreach.
alice7

12
Một trong những "l.someproperty" đó có nên là "p.someproperty" không?
Manos Dilaverakis

6
Điều này có thể được đơn giản hóa với customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 không chính xác. Các Anyphải là một Allnếu bạn muốn làm điều đó như thế. Điều này là do danh sách thứ hai có thể có mục từ danh sách đầu tiên, nhưng nếu nó không phải là mục đầu tiên được chọn thì nó sẽ ngay lập tức nhận được giá trị true cho p.someproperty != l.someproperty. Điều này dẫn đến các mục được trả lại tồn tại trong cả hai danh sách. Xấu hổ cho 6 người đã ủng hộ điều này.
Murphybro2

26

Tôi nghĩ điều quan trọng cần nhấn mạnh - sử dụng phương pháp Ngoại trừ sẽ trả về cho bạn các mục chỉ có trong mục đầu tiên mà không có mục trong mục thứ hai. Nó không trả về những phần tử không xuất hiện ở vị trí thứ hai.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

Nhưng nếu bạn muốn có sự khác biệt thực sự giữa hai danh sách:

Các mục có trong mục đầu tiên mà không có các mục trong mục thứ hai và các mục nằm trong mục thứ hai mà không có mục trong mục đầu tiên.

Bạn cần sử dụng Ngoại trừ hai lần:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Hoặc bạn có thể sử dụng phương thức SymmetricExceptWith của HashSet. Nhưng nó thay đổi tập hợp được gọi là:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

Thật là hữu ích. Chỉ cần biết rằng OP đã nói rõ rằng họ không muốn có sự khác biệt hoàn toàn: "Tôi cần truy xuất sự khác biệt giữa hai danh sách đó ( Các mục nằm trong danh sách đầu tiên mà không có các mục trong danh sách thứ hai )".
rsenna

10
var third = first.Except(second);

(bạn cũng có thể gọi ToList()sau Except(), nếu bạn không thích tham chiếu đến các bộ sưu tập lười biếng.)

Các Except()phương pháp so sánh giá trị sử dụng comparer mặc định, nếu các giá trị được so sánh là các kiểu dữ liệu cơ bản, chẳng hạn như int, string, decimal, vv

Nếu không, việc so sánh sẽ được thực hiện theo địa chỉ đối tượng, có thể không phải là điều bạn muốn ... Trong trường hợp đó, hãy thực hiện các đối tượng tùy chỉnh của bạn IComparable(hoặc triển khai một tùy chỉnhIEqualityComparer và chuyển nó vào Except()phương thức).


3
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Lưu ý: list3sẽ chứa các mục hoặc đối tượng không có trong cả hai danh sách. Lưu ý: Nó ToList()không phảitoList()


1

Vì phương thức mở rộng Ngoại trừ hoạt động trên hai IEumerables, đối với tôi, có vẻ như nó sẽ là một phép toán O (n ^ 2). Nếu hiệu suất là một vấn đề (nếu nói rằng danh sách của bạn lớn), tôi khuyên bạn nên tạo một HashSet từ list1 và sử dụng phương thức Ngoại trừ của HashSet.


9
Enumerable.Exceptsử dụng nội bộ HashSethoặc một cái gì đó tương tự. Nó chắc chắn không sử dụng thuật toán O (n ^ 2) ngây thơ.
Jim Mischel

@Jim Mischel: Bạn nói đúng, Enumerable.Except sử dụng cấu trúc dữ liệu Tập hợp nội bộ và thêm các mục từ cả IEnumerables vào Tập hợp. Ước gì tài liệu sẽ nói điều gì đó về điều đó.
foson

1

đây là giải pháp của tôi:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

hơi muộn nhưng đây là giải pháp làm việc cho tôi

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

Trong mã đề cập ở trên, tôi nhận được sự khác biệt về thuộc tính giữa lớp cơ sở của tôi và danh sách lớp dẫn xuất


-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

Lưu ý: Kết quả Danh sách là các mục mà tồn tại trong danh sách kiểm tra nhưng không phải trong myList
Teezy7

-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

6 năm sau khi trả lời chấp nhận và cho biết thêm không có gì mới có giá trị
Mitch Wheat

-3

Nếu cả hai danh sách của bạn đều triển khai giao diện IEnumerable, bạn có thể đạt được điều này bằng cách sử dụng LINQ.

list3 = list1.where(i => !list2.contains(i));

6
Và nếu mỗi danh sách của bạn chứa một triệu mục, bạn sẽ phải đợi rất lâu. Sử dụng IEnumerable.Exceptthay thế.
Jim Mischel

3
Vâng. Tôi chỉ có khoảng 450 mặt hàng và tôi vẫn đang đợi. Hãy cẩn thận khi sử dụng cái này.
Marnee KG7SIO

-3
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }
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.