Cách nhanh nhất để so sánh hai danh sách chung cho sự khác biệt


213

Cách nhanh nhất (và ít tốn tài nguyên nhất) để so sánh hai khối lượng lớn (> 50.000 mặt hàng) và kết quả là có hai danh sách như các danh sách dưới đây:

  1. các mục hiển thị trong danh sách đầu tiên nhưng không xuất hiện trong danh sách thứ hai
  2. các mục hiển thị trong danh sách thứ hai nhưng không xuất hiện trong danh sách thứ nhất

Hiện tại tôi đang làm việc với Danh sách hoặc IReadOnlyCollection và giải quyết vấn đề này trong truy vấn linq:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

Nhưng điều này không thực hiện tốt như tôi muốn. Bất kỳ ý tưởng làm cho điều này nhanh hơn và ít tốn tài nguyên hơn vì tôi cần xử lý rất nhiều danh sách?

Câu trả lời:


452

Sử dụng Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

Tôi nghi ngờ có những cách tiếp cận thực sự sẽ nhanh hơn một chút so với cách tiếp cận này, nhưng thậm chí điều này sẽ nhanh hơn rất nhiều so với cách tiếp cận O (N * M) của bạn.

Nếu bạn muốn kết hợp những thứ này, bạn có thể tạo một phương thức với ở trên và sau đó là một câu lệnh return:

return !firstNotSecond.Any() && !secondNotFirst.Any();

Một điểm cần lưu ý là có một sự khác biệt trong kết quả giữa các mã gốc trong các câu hỏi và giải pháp ở đây: bất kỳ yếu tố trùng lặp mà chỉ trong một danh sách sẽ chỉ được thông báo một lần với mã của tôi, trong khi đó họ sẽ được báo cáo như nhiều lần khi chúng xảy ra trong mã gốc.

Ví dụ, với danh sách [1, 2, 2, 2, 3][1], "các phần tử trong list1 chứ không phải list2" trong mã gốc sẽ là [2, 2, 2, 3]. Với mã của tôi, nó sẽ chỉ là [2, 3]. Trong nhiều trường hợp sẽ không phải là một vấn đề, nhưng nó đáng để nhận thức.


8
Đây thực sự là một hiệu suất rất lớn! Cảm ơn câu trả lời này.
Frank

2
Tôi đang tự hỏi cho hai danh sách lớn, nó có hữu ích để sắp xếp trước khi so sánh không? hoặc bên trong Ngoại trừ phương thức mở rộng, danh sách được truyền vào đã được sắp xếp.
Larry

9
@Larry: Nó không được sắp xếp; nó xây dựng một bộ băm.
Jon Skeet

2
@PranavSingh: Nó sẽ hoạt động cho bất kỳ thứ gì có sự bình đẳng phù hợp - vì vậy nếu loại tùy chỉnh của bạn ghi đè Equals(object)và / hoặc thực hiện IEquatable<T>thì nó sẽ ổn.
Jon Skeet

2
@ k2ibegin: Nó sử dụng trình so sánh đẳng thức mặc định, sẽ sử dụng phương thức IEquatable<T>thực hiện hoặc object.Equals(object)phương thức. Có vẻ như bạn nên tạo một câu hỏi mới với một ví dụ có thể lặp lại tối thiểu - chúng tôi thực sự không thể chẩn đoán mọi thứ trong các nhận xét.
Jon Skeet

40

Hiệu quả hơn sẽ được sử dụng Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

Phương pháp này được thực hiện bằng cách sử dụng thực thi hoãn lại. Điều đó có nghĩa là bạn có thể viết ví dụ:

var first10 = inListButNotInList2.Take(10);

Nó cũng hiệu quả vì bên trong nó sử dụng a Set<T>để so sánh các đối tượng. Nó hoạt động bằng cách đầu tiên thu thập tất cả các giá trị riêng biệt từ chuỗi thứ hai, sau đó truyền kết quả của lần đầu tiên, kiểm tra xem chúng chưa từng thấy trước đó.


1
Hừm. Không hoàn toàn hoãn lại. Tôi muốn nói một phần hoãn lại. Một bản hoàn chỉnh Set<T>được xây dựng từ chuỗi thứ hai (nghĩa là nó được lặp lại và lưu trữ đầy đủ), sau đó các mục có thể được thêm vào từ chuỗi đầu tiên được mang lại.
tiêu

2
@spender, điều đó giống như nói rằng việc thực thi Wherebị hoãn lại một phần vì trong list.Where(x => x.Id == 5)giá trị của số 5được lưu trữ khi bắt đầu, thay vì thực thi một cách lười biếng.
JWG

27

Phương pháp vô số.SequenceEqual

Xác định xem hai chuỗi có bằng nhau theo một so sánh bằng không. MS.Docs

Enumerable.SequenceEqual(list1, list2);

Điều này làm việc cho tất cả các loại dữ liệu nguyên thủy. Nếu bạn cần sử dụng nó trên các đối tượng tùy chỉnh, bạn cần thực hiệnIEqualityComparer

Xác định các phương thức để hỗ trợ so sánh các đối tượng cho sự bình đẳng.

Giao diện IEqualityComparer

Xác định các phương thức để hỗ trợ so sánh các đối tượng cho sự bình đẳng. MS.Docs cho IEqualityComparer


đây sẽ là câu trả lời được chấp nhận Câu hỏi không phải là về SETS mà là về LISTS, có thể chứa các phần tử trùng lặp.
Adrian Nasui

3
Tôi không thấy làm thế nào đây có thể là câu trả lời, vì kết quả của SequenceEqualnó là đơn giản bool. OP muốn có hai danh sách kết quả - và mô tả những gì họ muốn về các hoạt động đã đặt: "các mục hiển thị trong danh sách đầu tiên nhưng không phải trong danh sách thứ hai". Không có dấu hiệu cho thấy trật tự có liên quan, trong khi SequenceEqual không coi nó là có liên quan. Điều này dường như đang trả lời một câu hỏi hoàn toàn khác.
Jon Skeet

vâng, đúng, có vẻ như tôi đã trả lời câu hỏi này quá nhanh và không nhìn vào phần thứ hai của yêu cầu ... giống như hai bình luận đầu tiên ...
Miguelmpn

9

Nếu bạn muốn kết quả không phân biệt chữ hoa chữ thường , phần sau sẽ hoạt động:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondsẽ chứa b1.dll

secondNotFirstsẽ chứa b2.dll


5

Không phải cho vấn đề này, nhưng đây là một số mã để so sánh các danh sách cho bằng nhau và không! các đối tượng giống hệt nhau:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}

1
Đây là những gì bạn cần để có thể so sánh các loại dữ liệu tùy chỉnh. Sau đó sử dụngExcept
Pranav Singh

Bạn có thể có thể làm tốt hơn với các loại sắp xếp. Điều này chạy trong O (n ^ 2), trong khi bạn có thể làm O (nlogn).
yuvalm2

3

thử cách này:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

13
Điều này phải chịu hiệu suất khủng khiếp, yêu cầu quét danh sách thứ hai cho mọi mục trong lần đầu tiên. Không bỏ qua vì nó hoạt động, nhưng nó tệ như mã gốc.
tiêu

3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

Đôi khi bạn chỉ cần biết nếu hai danh sách là khác nhau, và không phải những khác biệt đó là gì. Trong trường hợp đó, hãy xem xét thêm phương thức mở rộng này vào dự án của bạn. Lưu ý rằng các đối tượng được liệt kê của bạn nên triển khai IEquitable!

Sử dụng:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Componentlớp học là gì, các phương thức hiển thị ở đây Carnên được thực hiện gần như giống hệt nhau.

Điều rất quan trọng cần lưu ý là chúng tôi đã viết GetHashCode như thế nào. Để thực hiện đúng IEquatable, EqualsGetHashCode phải hoạt động trên tài sản của ví dụ trong một cách tương thích một cách logic.

Hai danh sách có cùng nội dung vẫn là các đối tượng khác nhau và sẽ tạo ra các mã băm khác nhau. Vì chúng tôi muốn hai danh sách này được coi là bằng nhau, chúng tôi phải cho phép GetHashCodetạo ra cùng một giá trị cho mỗi danh sách. Chúng ta có thể thực hiện điều này bằng cách ủy thác mã băm cho mọi thành phần trong danh sách và sử dụng XOR bitwise tiêu chuẩn để kết hợp tất cả chúng. XOR không theo thứ tự, vì vậy sẽ không có vấn đề gì nếu các danh sách được sắp xếp khác nhau. Nó chỉ quan trọng rằng họ không chứa gì ngoài các thành viên tương đương.

Lưu ý: tên lạ là ngụ ý thực tế là phương thức không xem xét thứ tự của các phần tử trong danh sách. Nếu bạn quan tâm đến thứ tự của các yếu tố trong danh sách, phương pháp này không dành cho bạn!


1

Tôi đã sử dụng mã này để so sánh hai danh sách có hàng triệu hồ sơ.

Phương pháp này sẽ không mất nhiều thời gian

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }

0

Nếu chỉ kết hợp cần thiết, điều này cũng sẽ hoạt động:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

Trong đó T là loại phần tử danh sách.


-1

Có thể buồn cười, nhưng làm việc cho tôi

chuỗi.Join ("", List1)! = string.Join ("", List2)


vì nó được viết ở đây, nó thậm chí sẽ không hoạt động cho Danh sách <chuỗi> hoặc Danh sách <int>, ví dụ như hai danh sách 11; 2; 3 và 1; 12; 3 sẽ giống hệt nhau vì bạn không tham gia chuỗi với một số dấu phân cách duy nhất không phải là một mục có thể có trong danh sách. Ngoài ra, việc nối các chuỗi cho một danh sách với rất nhiều mục có lẽ là một kẻ giết người hiệu suất.
SwissCoder

@SwissCoder: Bạn đã nhầm, đây không phải là một kẻ giết người biểu diễn cho chuỗi. Nếu bạn có hai danh sách với 50.000 chuỗi (mỗi chuỗi dài 3) thì thuật toán này cần 3 ms trên máy của tôi. Các câu trả lời được chấp nhận cần 7. Tôi nghĩ mẹo là Jibz chỉ cần một chuỗi so sánh. Tất nhiên anh ta phải thêm một dấu phân cách duy nhất.
dùng1027167

@ user1027167: Tôi không nói về việc so sánh trực tiếp các chuỗi (vì đây cũng không phải là câu hỏi). Gọi phương thức .ToString () của tất cả các đối tượng trong Danh sách có 50.000 đối tượng có thể tạo ra một chuỗi lớn, tùy thuộc vào cách triển khai. Tôi không nghĩ đó là con đường để đi. Sau đó, cũng rất rủi ro khi dựa vào một ký tự hoặc chuỗi là "duy nhất", mã sẽ không thực sự được sử dụng lại như thế.
SwissCoder

Ok đó là sự thật. Người hỏi hỏi cách nhanh nhất mà không đưa ra kiểu dữ liệu trong danh sách của mình. Có lẽ câu trả lời này là cách nhanh nhất cho trường hợp sử dụng của người hỏi.
dùng1027167

-3

Tôi nghĩ rằng đây là một cách đơn giản và dễ dàng để so sánh hai yếu tố danh sách theo yếu tố

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)

3
Đây là câu hỏi về C # và bạn chưa cung cấp mã C #.
Wai Ha Lee

1
Có lẽ bạn có thể xóa câu trả lời này và chuyển nó đến (ví dụ) Làm thế nào tôi có thể so sánh hai danh sách trong python và return trùng khớp ?
Wai Ha Lee

-4

Đây là giải pháp tốt nhất bạn sẽ tìm thấy

var list3 = list1.Where(l => list2.ToList().Contains(l));

1
Điều này thực sự rất tệ vì nó tạo ra một cái mới List<T>cho mỗi yếu tố trong list1. Ngoài ra kết quả được gọi list3khi nó không phải là a List<T>.
Ái Hà Lee
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.