Có một phương pháp tích hợp để so sánh các bộ sưu tập?


178

Tôi muốn so sánh nội dung của một vài bộ sưu tập trong phương thức Equals của tôi. Tôi có một từ điển và một IList. Có một phương pháp tích hợp để làm điều này?

Đã chỉnh sửa: Tôi muốn so sánh hai Từ điển và hai ILists, vì vậy tôi nghĩ ý nghĩa của đẳng thức là rõ ràng - nếu hai từ điển chứa cùng một khóa được ánh xạ tới cùng một giá trị, thì chúng bằng nhau.


Bất kể trật tự hay không cho IList? Câu hỏi mơ hồ.
nawfal

Enumerable.SequenceEqualISet.SetEqualscung cấp các phiên bản của chức năng này. Nếu bạn muốn không theo thứ tự và làm việc với các bộ sưu tập có trùng lặp, bạn sẽ cần phải tự cuộn. Kiểm tra việc triển khai được đề xuất trong bài đăng này
ChaseMedallion

Câu trả lời:


185

Enumerable.SequenceEqual

Xác định xem hai chuỗi có bằng nhau hay không bằng cách so sánh các phần tử của chúng bằng cách sử dụng IEqualityComparer (T) được chỉ định.

Bạn không thể so sánh trực tiếp danh sách & từ điển, nhưng bạn có thể so sánh danh sách các giá trị từ Từ điển với danh sách


52
Vấn đề là SequenceEqual hy vọng các yếu tố sẽ theo cùng một thứ tự. Lớp Từ điển không đảm bảo thứ tự các khóa hoặc giá trị khi liệt kê, vì vậy nếu bạn sẽ sử dụng SequenceEqual, trước tiên bạn phải sắp xếp .Keys và .Values!
Orion Edwards

3
@Orion: ... trừ khi bạn muốn phát hiện sự khác biệt về thứ tự, tất nhiên :-)
schoetbi

30
@schoetbi: Tại sao bạn muốn phát hiện sự khác biệt đặt hàng trong một container không đảm bảo trật tự ?
Matti Virkkunen

4
@schoetbi: Đó là để lấy một yếu tố nhất định ra khỏi IEnumerable. Tuy nhiên, một từ điển không đảm bảo trật tự, do đó .Keys.Valuescó thể trả về các khóa và giá trị theo bất kỳ thứ tự nào chúng cảm thấy và thứ tự đó có thể sẽ thay đổi khi từ điển cũng được sửa đổi. Tôi đề nghị bạn nên đọc từ điển là gì và nó không phải là gì.
Matti Virkkunen

5
MS 'TestTools và NUnit cung cấp CollectionAssert.AreEquivalent
tymtam

44

Như những người khác đã đề xuất và đã lưu ý, SequenceEquallà nhạy cảm trật tự. Để giải quyết điều đó, bạn có thể sắp xếp từ điển theo khóa (là duy nhất và do đó sắp xếp luôn ổn định) và sau đó sử dụng SequenceEqual. Biểu thức sau đây kiểm tra nếu hai từ điển bằng nhau bất kể thứ tự nội bộ của chúng:

dictionary1.OrderBy(kvp => kvp.Key).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

EDIT: Như được chỉ ra bởi Jeppe Stig Nielsen, một số đối tượng có một IComparer<T>không tương thích với họ IEqualityComparer<T>, mang lại kết quả không chính xác. Khi sử dụng các khóa với một đối tượng như vậy, bạn phải chỉ định chính xác IComparer<T>cho các khóa đó. Ví dụ: với các khóa chuỗi (thể hiện vấn đề này), bạn phải làm như sau để có kết quả chính xác:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))

Nếu loại khóa sẽ không CompareTothì sao? Giải pháp của bạn sẽ bùng nổ sau đó. Điều gì xảy ra nếu loại khóa có một bộ so sánh mặc định không tương thích với bộ so sánh đẳng thức mặc định của nó? Đây là trường hợp cho string, bạn biết. Như một ví dụ, những từ điển này (với các so sánh bình đẳng mặc định ngầm) sẽ thất bại trong bài kiểm tra của bạn (theo tất cả các thông tin về văn hóa mà tôi biết):var dictionary1 = new Dictionary<string, int> { { "Strasse", 10 }, { "Straße", 20 }, }; var dictionary2 = new Dictionary<string, int> { { "Straße", 20 }, { "Strasse", 10 }, };
Jeppe Stig Nielsen

@JeppeStigNielsen: Về sự không tương thích giữa IComparerIEqualityComparer- Tôi không nhận thức được vấn đề này, rất thú vị! Tôi cập nhật câu trả lời với một giải pháp có thể. Về việc thiếu CompareTo, tôi nghĩ rằng nhà phát triển nên đảm bảo rằng đại biểu được cung cấp cho OrderBy()phương thức trả về một cái gì đó có thể so sánh được. Tôi nghĩ rằng điều này đúng cho bất kỳ việc sử dụng hoặc OrderBy(), thậm chí ngoài các so sánh từ điển.
Allon Guralnek

15

Ngoài SequenceEqual đã đề cập , mà

là đúng nếu hai danh sách có độ dài bằng nhau và các yếu tố tương ứng của chúng so sánh bằng nhau theo một so sánh

(có thể là bộ so sánh mặc định, nghĩa là ghi đè Equals() )

điều đáng nói là trong .Net4 có SetEquals trênISet các đối tượng, trong đó

bỏ qua thứ tự của các phần tử và bất kỳ phần tử trùng lặp.

Vì vậy, nếu bạn muốn có một danh sách các đối tượng, nhưng chúng không cần phải theo một thứ tự cụ thể, hãy xem xét rằng ISet(như a HashSet) có thể là lựa chọn đúng đắn.


7

Hãy xem phương pháp EnSable.SequenceEqual

var dictionary = new Dictionary<int, string>() {{1, "a"}, {2, "b"}};
var intList = new List<int> {1, 2};
var stringList = new List<string> {"a", "b"};
var test1 = dictionary.Keys.SequenceEqual(intList);
var test2 = dictionary.Values.SequenceEqual(stringList);

13
Điều này không đáng tin cậy vì SequenceEqual hy vọng các giá trị sẽ ra khỏi từ điển theo thứ tự đáng tin cậy - Từ điển không đảm bảo như vậy về thứ tự và dictionary.Keys có thể xuất hiện dưới dạng [2, 1] thay vì [1, 2] và bài kiểm tra của bạn sẽ thất bại
Orion Edwards

5

.NET thiếu bất kỳ công cụ mạnh mẽ nào để so sánh các bộ sưu tập. Tôi đã phát triển một giải pháp đơn giản mà bạn có thể tìm thấy ở liên kết dưới đây:

http://robertbouillon.com/2010/04/29/compared-collections-in-net/

Điều này sẽ thực hiện so sánh bằng bất kể thứ tự:

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

Điều này sẽ kiểm tra xem các mục đã được thêm / xóa:

var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added;   //Sally
var inbothlists = diff.Equal;   //Bob

Điều này sẽ thấy những mục trong từ điển đã thay đổi:

var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
  Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa

10
Tất nhiên .NET có các công cụ mạnh mẽ để so sánh các bộ sưu tập (chúng là các hoạt động dựa trên tập hợp). .Removedlà giống như list1.Except(list2), .Addedlist2.Except(list1), .Equallist1.Intersect(list2).Differentoriginal.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Bạn có thể làm hầu hết mọi so sánh với LINQ.
Allon Guralnek

3
Sửa chữa: .Differentoriginal.Join(changed, left => left.Key, right => right.Key, (left, right) => new { Key = left.Key, NewValue = right.Value, Different = left.Value == right.Value).Where(d => d.Different). Và bạn thậm chí có thể thêm OldValue = left.Valuenếu bạn cần giá trị cũ quá.
Allon Guralnek

3
@ ALLonGuralnek đề xuất của bạn là tốt, nhưng họ không xử lý trường hợp Danh sách không phải là một bộ thực sự - trong đó danh sách chứa cùng một đối tượng nhiều lần. So sánh {1, 2} và {1, 2, 2} sẽ không trả lại gì được thêm / xóa.
Niall Connaughton


4

Tôi không biết về phương pháp Enumerable.SequenceEqual (bạn học được điều gì đó mỗi ngày ....), nhưng tôi sẽ đề xuất sử dụng phương pháp mở rộng; đại loại như thế này:

    public static bool IsEqual(this List<int> InternalList, List<int> ExternalList)
    {
        if (InternalList.Count != ExternalList.Count)
        {
            return false;
        }
        else
        {
            for (int i = 0; i < InternalList.Count; i++)
            {
                if (InternalList[i] != ExternalList[i])
                    return false;
            }
        }

        return true;

    }

Thật thú vị, sau khi dành 2 giây để đọc về SequenceEqual, có vẻ như Microsoft đã xây dựng chức năng mà tôi mô tả cho bạn.


4

Đây không phải là trả lời trực tiếp câu hỏi của bạn, nhưng cả TestTools và NUnit của MS đều cung cấp

 CollectionAssert.AreEquivalent

mà không có nhiều những gì bạn muốn.


Đã tìm kiếm điều này cho bài kiểm tra NUnit của tôi
Blem

1

Để so sánh các bộ sưu tập, bạn cũng có thể sử dụng LINQ. Enumerable.Intersecttrả về tất cả các cặp bằng nhau Bạn có thể so sánh hai từ điển như thế này:

(dict1.Count == dict2.Count) && dict1.Intersect(dict2).Count() == dict1.Count

Sự so sánh đầu tiên là cần thiết bởi vì dict2có thể chứa tất cả các khóa từdict1 và hơn thế nữa.

Bạn cũng có thể sử dụng suy nghĩ về các biến thể bằng cách sử dụng Enumerable.ExceptEnumerable.Unionđiều đó dẫn đến kết quả tương tự. Nhưng có thể được sử dụng để xác định sự khác biệt chính xác giữa các bộ.


1

Làm thế nào về ví dụ này:

 static void Main()
{
    // Create a dictionary and add several elements to it.
    var dict = new Dictionary<string, int>();
    dict.Add("cat", 2);
    dict.Add("dog", 3);
    dict.Add("x", 4);

    // Create another dictionary.
    var dict2 = new Dictionary<string, int>();
    dict2.Add("cat", 2);
    dict2.Add("dog", 3);
    dict2.Add("x", 4);

    // Test for equality.
    bool equal = false;
    if (dict.Count == dict2.Count) // Require equal count.
    {
        equal = true;
        foreach (var pair in dict)
        {
            int value;
            if (dict2.TryGetValue(pair.Key, out value))
            {
                // Require value be equal.
                if (value != pair.Value)
                {
                    equal = false;
                    break;
                }
            }
            else
            {
                // Require key be present.
                equal = false;
                break;
            }
        }
    }
    Console.WriteLine(equal);
}

Lịch sự: https://www.dotnetperls.com/dipedia-equals


value! = cặp.Value đang thực hiện so sánh tham chiếu, thay vào đó hãy sử dụng Equals
kofifus

1

Đối với các bộ sưu tập theo thứ tự (Danh sách, Mảng) sử dụng SequenceEqual

để sử dụng Hashset SetEquals

cho từ điển bạn có thể làm:

namespace System.Collections.Generic {
  public static class ExtensionMethods {
    public static bool DictionaryEquals<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> d1, IReadOnlyDictionary<TKey, TValue> d2) {
      if (object.ReferenceEquals(d1, d2)) return true; 
      if (d2 is null || d1.Count != d2.Count) return false;
      foreach (var (d1key, d1value) in d1) {
        if (!d2.TryGetValue(d1key, out TValue d2value)) return false;
        if (!d1value.Equals(d2value)) return false;
      }
      return true;
    }
  }
}

(Một giải pháp tối ưu hơn sẽ sử dụng phân loại nhưng sẽ yêu cầu IComparable<TValue>)


0

Không. Khung bộ sưu tập không có bất kỳ khái niệm về bình đẳng. Nếu bạn nghĩ về nó, không có cách nào để so sánh các bộ sưu tập không chủ quan. Ví dụ, so sánh IList của bạn với Từ điển của bạn, liệu chúng có bằng nhau không nếu tất cả các khóa đều nằm trong IList, tất cả các giá trị đều nằm trong IList hoặc nếu cả hai đều nằm trong IList? Không có cách rõ ràng nào để so sánh hai bộ sưu tập này mà không có kiến ​​thức về những gì chúng sẽ được sử dụng cho mục đích chung tương đương với phương pháp không có ý nghĩa.



0
public bool CompareStringLists(List<string> list1, List<string> list2)
{
    if (list1.Count != list2.Count) return false;

    foreach(string item in list1)
    {
        if (!list2.Contains(item)) return false;
    }

    return true;
}

0

Không có, không và có thể không, ít nhất là tôi sẽ tin như vậy. Lý do đằng sau là sự bình đẳng trong bộ sưu tập có lẽ là một hành vi do người dùng xác định.

Các yếu tố trong các bộ sưu tập không được coi là theo một thứ tự cụ thể mặc dù chúng có thứ tự một cách tự nhiên, đó không phải là thuật toán so sánh nên dựa vào. Giả sử bạn có hai bộ sưu tập:

{1, 2, 3, 4}
{4, 3, 2, 1}

Họ có bằng nhau hay không? Bạn phải biết nhưng tôi không biết quan điểm của bạn là gì.

Các bộ sưu tập được sắp xếp theo khái niệm theo mặc định, cho đến khi các thuật toán cung cấp các quy tắc sắp xếp. Điều tương tự máy chủ SQL sẽ khiến bạn chú ý là khi bạn cố gắng phân trang, nó yêu cầu bạn cung cấp các quy tắc sắp xếp:

https://docs.microsoft.com/en-US/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017

Một bộ sưu tập khác:

{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}

Một lần nữa, họ có bằng nhau hay không? Bạn nói với tôi ..

Độ lặp lại thành phần của bộ sưu tập đóng vai trò của nó trong các tình huống khác nhau và một số bộ sưu tập như Dictionary<TKey, TValue>thậm chí không cho phép các yếu tố lặp lại.

Tôi tin rằng các loại bình đẳng này là ứng dụng được xác định và do đó khung không cung cấp tất cả các triển khai có thể.

Vâng, trong trường hợp chung Enumerable.SequenceEquallà đủ tốt nhưng nó trả về sai trong trường hợp sau:

var a = new Dictionary<String, int> { { "2", 2 }, { "1", 1 }, };
var b = new Dictionary<String, int> { { "1", 1 }, { "2", 2 }, };
Debug.Print("{0}", a.SequenceEqual(b)); // false

Tôi đọc một số câu trả lời cho các câu hỏi như thế này (bạn có thể google cho họ) và những gì tôi sẽ sử dụng, nói chung:

public static class CollectionExtensions {
    public static bool Represents<T>(this IEnumerable<T> first, IEnumerable<T> second) {
        if(object.ReferenceEquals(first, second)) {
            return true;
        }

        if(first is IOrderedEnumerable<T> && second is IOrderedEnumerable<T>) {
            return Enumerable.SequenceEqual(first, second);
        }

        if(first is ICollection<T> && second is ICollection<T>) {
            if(first.Count()!=second.Count()) {
                return false;
            }
        }

        first=first.OrderBy(x => x.GetHashCode());
        second=second.OrderBy(x => x.GetHashCode());
        return CollectionExtensions.Represents(first, second);
    }
}

Điều đó có nghĩa là một bộ sưu tập đại diện cho các bộ phận khác trong các yếu tố của chúng bao gồm cả thời gian lặp đi lặp lại mà không tính đến thứ tự ban đầu. Một số lưu ý khi thực hiện:

  • GetHashCode()chỉ dành cho trật tự không vì sự bình đẳng; Tôi nghĩ rằng nó là đủ trong trường hợp này

  • Count() sẽ không thực sự liệt kê bộ sưu tập và trực tiếp rơi vào việc thực hiện tài sản của ICollection<T>.Count

  • Nếu các tham chiếu bằng nhau, thì đó chỉ là Boris

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.