So sánh hai bộ sưu tập cho sự bình đẳng không phân biệt thứ tự của các mục trong đó


162

Tôi muốn so sánh hai bộ sưu tập (bằng C #), nhưng tôi không chắc chắn cách tốt nhất để thực hiện điều này một cách hiệu quả.

Tôi đã đọc các chủ đề khác về EnSable.SequenceEqual , nhưng nó không chính xác là những gì tôi đang tìm kiếm.

Trong trường hợp của tôi, hai bộ sưu tập sẽ bằng nhau nếu cả hai đều chứa cùng một mặt hàng (bất kể thứ tự).

Thí dụ:

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1 == collection2; // true

Những gì tôi thường làm là lặp qua từng mục của một bộ sưu tập và xem nó có tồn tại trong bộ sưu tập khác không, sau đó lặp qua từng mục của bộ sưu tập khác và xem liệu nó có tồn tại trong bộ sưu tập đầu tiên không. (Tôi bắt đầu bằng cách so sánh độ dài).

if (collection1.Count != collection2.Count)
    return false; // the collections are not equal

foreach (Item item in collection1)
{
    if (!collection2.Contains(item))
        return false; // the collections are not equal
}

foreach (Item item in collection2)
{
    if (!collection1.Contains(item))
        return false; // the collections are not equal
}

return true; // the collections are equal

Tuy nhiên, điều này không hoàn toàn chính xác và có lẽ đây không phải là cách hiệu quả nhất để so sánh hai bộ sưu tập cho sự bình đẳng.

Một ví dụ tôi có thể nghĩ rằng điều đó sẽ sai là:

collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}

Mà sẽ bằng với việc thực hiện của tôi. Tôi có nên đếm số lần mỗi mục được tìm thấy và đảm bảo số lượng bằng nhau trong cả hai bộ sưu tập không?


Các ví dụ có trong một số loại C # (hãy gọi nó là giả C #), nhưng đưa ra câu trả lời của bạn bằng bất kỳ ngôn ngữ nào bạn muốn, điều đó không thành vấn đề.

Lưu ý: Tôi đã sử dụng các số nguyên trong các ví dụ để đơn giản, nhưng tôi cũng muốn có thể sử dụng các đối tượng kiểu tham chiếu (chúng không hoạt động chính xác như các khóa vì chỉ so sánh tham chiếu của đối tượng, không phải nội dung).


1
Làm thế nào về thuật toán? Tất cả các câu trả lời liên quan bằng cách so sánh một cái gì đó, danh sách chung so sánh linq, vv Thực sự chúng ta đã hứa với ai đó rằng chúng ta sẽ không bao giờ sử dụng thuật toán như một lập trình viên lỗi thời?
Nuri YILMAZ

Bạn không kiểm tra Bình đẳng mà bạn đang kiểm tra Tương đương. Đó là nitpicky nhưng một sự khác biệt quan trọng. Và một thời gian dài trước đây. Đây là một Q + A tốt.
CAD bloke

Bạn có thể quan tâm đến bài đăng này , trong đó thảo luận về một phiên bản điều chỉnh của phương pháp dựa trên từ điển được mô tả dưới đây. Một vấn đề với hầu hết các cách tiếp cận từ điển đơn giản là chúng không xử lý null đúng vì lớp Dictionary của .NET không cho phép các khóa null.
ChaseMedallion

Câu trả lời:


112

Hóa ra Microsoft đã bao gồm điều này trong khung thử nghiệm của mình: CollectionAssert.AreEquivalent

Nhận xét

Hai bộ sưu tập là tương đương nếu chúng có cùng các yếu tố trong cùng một số lượng, nhưng theo bất kỳ thứ tự nào. Các phần tử bằng nhau nếu giá trị của chúng bằng nhau, không phải nếu chúng tham chiếu đến cùng một đối tượng.

Sử dụng phản xạ, tôi đã sửa đổi mã đằng sau AreEquivalent () để tạo ra một bộ so sánh đẳng thức tương ứng. Nó đầy đủ hơn các câu trả lời hiện có, vì nó đưa null vào tài khoản, thực hiện IEqualityComparer và có một số kiểm tra trường hợp hiệu quả và cạnh. Ngoài ra, đó là Microsoft :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private readonly IEqualityComparer<T> m_comparer;
    public MultiSetComparer(IEqualityComparer<T> comparer = null)
    {
        m_comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

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

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstNullCount;
        int secondNullCount;

        var firstElementCounts = GetElementCounts(first, out firstNullCount);
        var secondElementCounts = GetElementCounts(second, out secondNullCount);

        if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            var firstElementCount = kvp.Value;
            int secondElementCount;
            secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

            if (firstElementCount != secondElementCount)
                return true;
        }

        return false;
    }

    private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>(m_comparer);
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + (val?.GetHashCode() ?? 42);

        return hash;
    }
}

Sử dụng mẫu:

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

Hoặc nếu bạn chỉ muốn so sánh trực tiếp hai bộ sưu tập:

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

Cuối cùng, bạn có thể sử dụng một công cụ so sánh bình đẳng mà bạn chọn:

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

7
Tôi không chắc chắn 100% nhưng tôi nghĩ câu trả lời của bạn vi phạm các điều khoản sử dụng của Microsoft so với kỹ thuật đảo ngược.
Ian Dallas

1
Xin chào Ohad, Xin vui lòng đọc các cuộc tranh luận lâu sau trong chủ đề này, stackoverflow.com/questions/371328/... Nếu bạn thay đổi đối tượng hashcode, trong khi của nó trong một HashSet nó sẽ làm gián đoạn với các hành động thích hợp HashSet và có thể gây ra một ngoại lệ. Quy tắc như sau: Nếu hai đối tượng bằng nhau - chúng phải có cùng mã băm. Nếu hai đối tượng có cùng mã băm - không bắt buộc chúng phải bằng nhau. Hashcode phải giữ nguyên trong suốt cuộc đời của đối tượng! Đó là lý do tại sao bạn thúc đẩy IComparizable và IEqualrity.
James Roeiter

2
@JamesRoeiter Có lẽ nhận xét của tôi là sai lệch. Khi một từ điển gặp mã băm mà nó đã chứa, nó sẽ kiểm tra sự bằng nhau thực sự với EqualityComparer(một trong những bạn đã cung cấp hoặc EqualityComparer.Default, bạn có thể kiểm tra Reflector hoặc nguồn tham chiếu để xác minh điều này). Đúng, nếu các đối tượng thay đổi (và cụ thể là thay đổi mã băm) trong khi phương thức này đang chạy thì kết quả thật bất ngờ, nhưng điều đó chỉ có nghĩa là phương thức này không an toàn trong luồng này.
Ohad Schneider

1
@JamesRoeiter Giả sử x và y là hai đối tượng chúng ta muốn so sánh. Nếu chúng có mã băm khác nhau, chúng tôi biết chúng khác nhau (vì các mục bằng nhau có mã băm bằng nhau) và cách thực hiện ở trên là chính xác. Nếu chúng có cùng mã băm, việc thực hiện từ điển sẽ kiểm tra sự bằng nhau thực tế bằng cách sử dụng chỉ định EqualityComparer(hoặc EqualityComparer.Defaultnếu không được chỉ định) và một lần nữa việc thực hiện là chính xác.
Ohad Schneider

1
@CADbloke phương thức phải được đặt tên EqualsIEqualityComparer<T>giao diện. Những gì bạn nên xem là tên của chính bộ so sánh . Trong trường hợp này, nó MultiSetComparercó ý nghĩa.
Ohad Schneider

98

Một giải pháp đơn giản và khá hiệu quả là sắp xếp cả hai bộ sưu tập và sau đó so sánh chúng cho bằng nhau:

bool equal = collection1.OrderBy(i => i).SequenceEqual(
                 collection2.OrderBy(i => i));

Thuật toán này là O (N * logN), trong khi giải pháp của bạn ở trên là O (N ^ 2).

Nếu các bộ sưu tập có các thuộc tính nhất định, bạn có thể thực hiện một giải pháp nhanh hơn. Ví dụ: nếu cả hai bộ sưu tập của bạn là bộ băm, chúng không thể chứa các bản sao. Ngoài ra, kiểm tra xem một bộ băm có chứa một số phần tử là rất nhanh. Trong trường hợp đó, một thuật toán tương tự như thuật toán của bạn có thể sẽ nhanh nhất.


1
Bạn chỉ cần thêm một System.Linq; đầu tiên để làm cho nó hoạt động
Junior Mayhé

nếu mã này nằm trong một vòng lặp và bộ sưu tập1 được cập nhật và bộ sưu tập 2 vẫn chưa được xử lý, hãy chú ý ngay cả khi cả hai bộ sưu tập có cùng một đối tượng, trình gỡ lỗi sẽ hiển thị sai cho biến "bằng" này.
Junior Mayhé

5
@Chaulky - Tôi tin rằng OrderBy là cần thiết. Xem: dotnetfiddle.net/jA8iwE
Brett

Câu trả lời nào khác được gọi là "ở trên"? Có thể stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs

32

Tạo một từ điển "dict" và sau đó cho mỗi thành viên trong bộ sưu tập đầu tiên, làm dict [thành viên] ++;

Sau đó, lặp qua bộ sưu tập thứ hai theo cùng một cách, nhưng đối với mỗi thành viên, hãy thực hiện [thành viên] -.

Cuối cùng, lặp lại tất cả các thành viên trong từ điển:

    private bool SetEqual (List<int> left, List<int> right) {

        if (left.Count != right.Count)
            return false;

        Dictionary<int, int> dict = new Dictionary<int, int>();

        foreach (int member in left) {
            if (dict.ContainsKey(member) == false)
                dict[member] = 1;
            else
                dict[member]++;
        }

        foreach (int member in right) {
            if (dict.ContainsKey(member) == false)
                return false;
            else
                dict[member]--;
        }

        foreach (KeyValuePair<int, int> kvp in dict) {
            if (kvp.Value != 0)
                return false;
        }

        return true;

    }

Chỉnh sửa: Theo như tôi có thể nói đây là cùng một thứ tự như thuật toán hiệu quả nhất. Thuật toán này là O (N), giả sử rằng Từ điển sử dụng tra cứu O (1).


Đây gần như là những gì tôi muốn. Tuy nhiên, tôi muốn có thể làm điều này ngay cả khi tôi không sử dụng số nguyên. Tôi muốn sử dụng các đối tượng tham chiếu, nhưng chúng không hoạt động đúng như các khóa trong từ điển.
mbillard

Mono, câu hỏi của bạn là tranh luận nếu Mục của bạn không thể so sánh được. Nếu chúng không thể được sử dụng làm khóa trong Từ điển, thì không có giải pháp nào khả dụng.
skolima

1
Tôi nghĩ Mono có nghĩa là các phím không thể sắp xếp. Nhưng giải pháp của Daniel rõ ràng là được thực hiện với hàm băm chứ không phải cây và sẽ hoạt động miễn là có một bài kiểm tra tương đương và hàm băm.
erickson

Tất nhiên được nâng cấp để được giúp đỡ, nhưng không được chấp nhận vì nó thiếu một điểm quan trọng (mà tôi bao gồm trong câu trả lời của mình).
mbillard

1
FWIW, bạn có thể đơn giản hóa vòng lặp foreach cuối cùng của mình và tuyên bố trả về bằng cách này:return dict.All(kvp => kvp.Value == 0);
Tyson Williams

18

Đây là cách thực hiện chung của tôi (chịu ảnh hưởng nhiều từ D.Jennings) của phương pháp so sánh (trong C #):

/// <summary>
/// Represents a service used to compare two collections for equality.
/// </summary>
/// <typeparam name="T">The type of the items in the collections.</typeparam>
public class CollectionComparer<T>
{
    /// <summary>
    /// Compares the content of two collections for equality.
    /// </summary>
    /// <param name="foo">The first collection.</param>
    /// <param name="bar">The second collection.</param>
    /// <returns>True if both collections have the same content, false otherwise.</returns>
    public bool Execute(ICollection<T> foo, ICollection<T> bar)
    {
        // Declare a dictionary to count the occurence of the items in the collection
        Dictionary<T, int> itemCounts = new Dictionary<T,int>();

        // Increase the count for each occurence of the item in the first collection
        foreach (T item in foo)
        {
            if (itemCounts.ContainsKey(item))
            {
                itemCounts[item]++;
            }
            else
            {
                itemCounts[item] = 1;
            }
        }

        // Wrap the keys in a searchable list
        List<T> keys = new List<T>(itemCounts.Keys);

        // Decrease the count for each occurence of the item in the second collection
        foreach (T item in bar)
        {
            // Try to find a key for the item
            // The keys of a dictionary are compared by reference, so we have to
            // find the original key that is equivalent to the "item"
            // You may want to override ".Equals" to define what it means for
            // two "T" objects to be equal
            T key = keys.Find(
                delegate(T listKey)
                {
                    return listKey.Equals(item);
                });

            // Check if a key was found
            if(key != null)
            {
                itemCounts[key]--;
            }
            else
            {
                // There was no occurence of this item in the first collection, thus the collections are not equal
                return false;
            }
        }

        // The count of each item should be 0 if the contents of the collections are equal
        foreach (int value in itemCounts.Values)
        {
            if (value != 0)
            {
                return false;
            }
        }

        // The collections are equal
        return true;
    }
}

12
Công việc tốt, nhưng Lưu ý: 1. Trái ngược với giải pháp Daniel Jennings, Đây không phải là O (N) mà là O (N ^ 2), vì chức năng tìm bên trong vòng lặp foreach trên bộ sưu tập thanh; 2. Bạn có thể khái quát hóa phương thức để chấp nhận IEnumerable <T> thay vì ICollection <T> mà không cần sửa đổi thêm cho mã
Ohad Schneider

The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"- Đây không phải là sự thật. Thuật toán dựa trên các giả định sai và trong khi hoạt động, nó rất kém hiệu quả.
Antonín Lejsek

10

Bạn có thể sử dụng Hashset . Nhìn vào phương thức SetEquals .


2
tất nhiên, sử dụng Hashset giả định không có sự trùng lặp nhưng nếu vậy Hashset là cách tốt nhất để đi
Mark Cidade

7

Nếu bạn sử dụng Shouldly , bạn có thể sử dụng ShouldAllBe với Chứa.

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1.ShouldAllBe(item=>collection2.Contains(item)); // true

Và cuối cùng, bạn có thể viết một phần mở rộng.

public static class ShouldlyIEnumerableExtensions
{
    public static void ShouldEquivalentTo<T>(this IEnumerable<T> list, IEnumerable<T> equivalent)
    {
        list.ShouldAllBe(l => equivalent.Contains(l));
    }
}

CẬP NHẬT

Một tham số tùy chọn tồn tại trên phương thức ShouldBe .

collection1.ShouldBe(collection2, ignoreOrder: true); // true

1
Tôi vừa tìm thấy trên phiên bản mới nhất có một tham số bool ignoreOrdervề phương thức ShouldBe .
Cầu tàu-Lionel Sgard

5

EDIT: Tôi nhận ra ngay khi tôi đặt ra rằng điều này thực sự chỉ hoạt động cho các bộ - nó sẽ không xử lý đúng với các bộ sưu tập có các mục trùng lặp. Ví dụ {1, 1, 2} và {2, 2, 1} sẽ được coi là bằng nhau theo quan điểm của thuật toán này. Tuy nhiên, nếu bộ sưu tập của bạn là tập hợp (hoặc có thể đo bằng nhau theo cách đó), tôi hy vọng bạn thấy hữu ích dưới đây.

Giải pháp tôi sử dụng là:

return c1.Count == c2.Count && c1.Intersect(c2).Count() == c1.Count;

Linq làm điều từ điển dưới bìa, vì vậy đây cũng là O (N). (Lưu ý, đó là O (1) nếu các bộ sưu tập không cùng kích thước).

Tôi đã thực hiện kiểm tra độ tỉnh táo bằng phương pháp "SetEqual" được đề xuất bởi Daniel, phương thức OrderBy / SequenceEquals được đề xuất bởi Igor và đề xuất của tôi. Các kết quả bên dưới, hiển thị O (N * LogN) cho Igor và O (N) cho tôi và Daniel.

Tôi nghĩ rằng sự đơn giản của mã giao cắt Linq làm cho nó trở thành giải pháp thích hợp hơn.

__Test Latency(ms)__
N, SetEquals, OrderBy, Intersect    
1024, 0, 0, 0    
2048, 0, 0, 0    
4096, 31.2468, 0, 0    
8192, 62.4936, 0, 0    
16384, 156.234, 15.6234, 0    
32768, 312.468, 15.6234, 46.8702    
65536, 640.5594, 46.8702, 31.2468    
131072, 1312.3656, 93.7404, 203.1042    
262144, 3765.2394, 187.4808, 187.4808    
524288, 5718.1644, 374.9616, 406.2084    
1048576, 11420.7054, 734.2998, 718.6764    
2097152, 35090.1564, 1515.4698, 1484.223

Vấn đề duy nhất với mã này là nó chỉ hoạt động khi so sánh các loại giá trị hoặc so sánh các con trỏ với các loại tham chiếu. Tôi có thể có hai phiên bản khác nhau của cùng một đối tượng trong các bộ sưu tập, vì vậy tôi cần có thể chỉ định cách so sánh từng đối tượng. Bạn có thể vượt qua một đại biểu so sánh cho phương pháp giao nhau?
mbillard

Chắc chắn, bạn có thể vượt qua một đại biểu so sánh. Nhưng, lưu ý giới hạn trên liên quan đến các bộ mà tôi đã thêm, điều này đặt ra một giới hạn đáng kể về khả năng áp dụng của nó.

Phương thức Intersect trả về một bộ sưu tập riêng biệt. Cho a = {1,1,2} và b = {2,2,1}, a.Intersect (b) .Count ()! = A.Count, khiến biểu thức của bạn trả về sai. {1,2} .Count! = {1,1,2} .Count Xem liên kết [/ link] (Lưu ý rằng cả hai bên được phân biệt trước khi so sánh.)
Griffin

5

Trong trường hợp không lặp lại và không có thứ tự, EqualityComparer sau đây có thể được sử dụng để cho phép các bộ sưu tập làm khóa từ điển:

public class SetComparer<T> : IEqualityComparer<IEnumerable<T>> 
where T:IComparable<T>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == second)
            return true;
        if ((first == null) || (second == null))
            return false;
        return first.ToHashSet().SetEquals(second);
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

Đây là cách triển khai ToHashSet () mà tôi đã sử dụng. Các thuật toán mã băm xuất phát từ hiệu quả Java (bằng cách Jon Skeet).


Điểm nối tiếp cho lớp so sánh là gì? : o Ngoài ra, bạn có thể thay đổi đầu vào ISet<T>để thể hiện nó có nghĩa là cho các bộ (tức là không trùng lặp).
nawfal

@nawfal cảm ơn, không biết tôi đã nghĩ gì khi đánh dấu nó Nối tiếp ... Về phần ISet, ý tưởng ở đây là coi bộ IEnumerablenhư một bộ (vì bạn IEnumerablephải bắt đầu bằng), mặc dù đã xem xét 0 upvote 5 năm có thể không phải là ý tưởng sắc nét nhất: P
Ohad Schneider

4
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) {
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);
}

Giải pháp yêu cầu .NET 3.5 và System.Collections.Generickhông gian tên. Theo Microsoft , SymmetricExceptWithlà một hoạt động O (n + m) , với n đại diện cho số phần tử trong tập đầu tiên và m đại diện cho số phần tử trong phần thứ hai. Bạn luôn có thể thêm một bộ so sánh bằng cho hàm này nếu cần.


3

Tại sao không sử dụng .Except ()

// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =   names1.Except(names2);
// Execute the query.
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
     Console.WriteLine(s);

http://msdn.microsoft.com/en-us/l Library / bb39894.aspx


2
Exceptsẽ không làm việc để đếm các mục trùng lặp. Nó sẽ trả về true cho các bộ {1,2,2} và {1,1,2}.
Cristian Diaconescu

@CristiDiaconescu trước tiên bạn có thể thực hiện ".Distinc ()" để xóa bất kỳ bản sao nào
Korayem

OP đang yêu cầu [1, 1, 2] != [1, 2, 2]. Sử dụng Distinctsẽ làm cho chúng trông như nhau.
Cristian Diaconescu

2

Một bài đăng trùng lặp, nhưng kiểm tra giải pháp của tôi để so sánh các bộ sưu tập . Nó khá đơn giản:

Đ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

Bài gốc ở đây .


1

erickson gần như đúng: vì bạn muốn khớp với số lượng trùng lặp, bạn muốn có một Túi . Trong Java, nó trông giống như:

(new HashBag(collection1)).equals(new HashBag(collection2))

Tôi chắc chắn C # có triển khai Cài đặt sẵn. Tôi sẽ sử dụng nó đầu tiên; nếu hiệu suất là một vấn đề, bạn luôn có thể sử dụng cài đặt Set khác, nhưng sử dụng cùng giao diện Set.


1

Đây là biến thể phương thức mở rộng của câu trả lời của ohadsc, trong trường hợp nó hữu ích với ai đó

static public class EnumerableExtensions 
{
    static public bool IsEquivalentTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if ((first == null) != (second == null))
            return false;

        if (!object.ReferenceEquals(first, second) && (first != null))
        {
            if (first.Count() != second.Count())
                return false;

            if ((first.Count() != 0) && HaveMismatchedElement<T>(first, second))
                return false;
        }

        return true;
    }

    private static bool HaveMismatchedElement<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts<T>(first, out firstCount);
        var secondElementCounts = GetElementCounts<T>(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts<T>(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    static private int GetHashCode<T>(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

Điều này thực hiện tốt như thế nào, bất kỳ ý tưởng?
nawfal

Tôi chỉ sử dụng điều này cho các bộ sưu tập nhỏ, vì vậy chưa nghĩ đến độ phức tạp của Big-O hoặc thực hiện đo điểm chuẩn. Chỉ riêng HaveMismatchedElements là O (M * N) nên có thể không hoạt động tốt cho các bộ sưu tập lớn.
Eric J.

Nếu IEnumerable<T>s là truy vấn thì gọi Count()không phải là một ý tưởng tốt. Cách tiếp cận câu trả lời ban đầu của Ohad là kiểm tra xem chúng có phải ICollection<T>là ý tưởng tốt hơn không.
nawfal

1

Đây là một giải pháp cải tiến so với giải pháp này .

public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        IEqualityComparer<T> comparer = null)
    {
        var firstMap = first
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        var secondMap = second
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        if (firstMap.Keys.Count != secondMap.Keys.Count)
            return false;

        if (firstMap.Keys.Any(k1 => !secondMap.ContainsKey(k1)))
            return false;

        return firstMap.Keys.All(x => firstMap[x] == secondMap[x]);
    }

0

Có nhiều giải pháp cho vấn đề này. Nếu bạn không quan tâm đến các bản sao, bạn không phải sắp xếp cả hai. Trước tiên hãy chắc chắn rằng họ có cùng số lượng mặt hàng. Sau đó sắp xếp một trong các bộ sưu tập. Sau đó binsearch từng mục từ bộ sưu tập thứ hai trong bộ sưu tập được sắp xếp. Nếu bạn không tìm thấy một mục dừng lại và trả lại sai. Sự phức tạp của việc này: - sắp xếp bộ sưu tập đầu tiên: N Log (N) - tìm kiếm từng mục từ thứ hai đến thứ nhất: NLOG (N) để bạn kết thúc với 2 * N * LOG (N) giả sử rằng chúng khớp và bạn tra cứu mọi thứ. Điều này tương tự như sự phức tạp của việc sắp xếp cả hai. Ngoài ra, điều này mang lại cho bạn lợi ích để dừng lại sớm hơn nếu có sự khác biệt. Tuy nhiên, hãy nhớ rằng nếu cả hai được sắp xếp trước khi bạn bước vào so sánh này và bạn thử sắp xếp bằng cách sử dụng một cái gì đó như qsort, thì việc sắp xếp sẽ tốn kém hơn. Có tối ưu hóa cho điều này. Một lựa chọn khác, rất phù hợp cho các bộ sưu tập nhỏ nơi bạn biết phạm vi của các yếu tố là sử dụng chỉ mục bitmask. Điều này sẽ cung cấp cho bạn một hiệu suất O (n). Một cách khác là sử dụng hàm băm và tra cứu nó. Đối với các bộ sưu tập nhỏ, thường là tốt hơn rất nhiều để thực hiện sắp xếp hoặc chỉ mục bitmask. Hashtable có nhược điểm của địa phương tồi tệ hơn vì vậy hãy ghi nhớ điều đó. Một lần nữa, đó chỉ là khi bạn không ' t quan tâm đến các bản sao. Nếu bạn muốn tính toán các bản sao, hãy sắp xếp cả hai.


0

Trong nhiều trường hợp, câu trả lời phù hợp duy nhất là câu trả lời của Igor Ostrovsky, các câu trả lời khác dựa trên mã băm đối tượng. Nhưng khi bạn tạo mã băm cho một đối tượng, bạn chỉ làm như vậy dựa trên các trường IMMUTABLE của anh ấy - chẳng hạn như trường Id đối tượng (trong trường hợp thực thể cơ sở dữ liệu) - Tại sao điều quan trọng là ghi đè GetHashCode khi phương thức Equals bị ghi đè?

Điều này có nghĩa là, nếu bạn so sánh hai bộ sưu tập, kết quả có thể đúng với phương pháp so sánh mặc dù các trường của các mục khác nhau không bằng nhau. Để so sánh sâu các bộ sưu tập, bạn cần sử dụng phương pháp của Igor và triển khai IEqualirity.

Xin vui lòng đọc các bình luận của tôi và mr.Schnider trên bài đăng được bình chọn nhiều nhất của anh ấy.

James


0

Cho phép các bản sao trong IEnumerable<T>(nếu các bộ không mong muốn \ có thể) và "bỏ qua thứ tự", bạn sẽ có thể sử dụng a .GroupBy().

Tôi không phải là chuyên gia về các phép đo phức tạp, nhưng hiểu biết thô sơ của tôi là đây phải là O (n). Tôi hiểu O (n ^ 2) là do thực hiện thao tác O (n) bên trong một hoạt động O (n) khác như thế nào ListA.Where(a => ListB.Contains(a)).ToList(). Mỗi mục trong ListB được đánh giá sự bình đẳng so với từng mục trong ListA.

Như tôi đã nói, sự hiểu biết của tôi về sự phức tạp còn hạn chế, vì vậy hãy sửa tôi nếu điều này sai.

public static bool IsSameAs<T, TKey>(this IEnumerable<T> source, IEnumerable<T> target, Expression<Func<T, TKey>> keySelectorExpression)
    {
        // check the object
        if (source == null && target == null) return true;
        if (source == null || target == null) return false;

        var sourceList = source.ToList();
        var targetList = target.ToList();

        // check the list count :: { 1,1,1 } != { 1,1,1,1 }
        if (sourceList.Count != targetList.Count) return false;

        var keySelector = keySelectorExpression.Compile();
        var groupedSourceList = sourceList.GroupBy(keySelector).ToList();
        var groupedTargetList = targetList.GroupBy(keySelector).ToList();

        // check that the number of grouptings match :: { 1,1,2,3,4 } != { 1,1,2,3,4,5 }
        var groupCountIsSame = groupedSourceList.Count == groupedTargetList.Count;
        if (!groupCountIsSame) return false;

        // check that the count of each group in source has the same count in target :: for values { 1,1,2,3,4 } & { 1,1,1,2,3,4 }
        // key:count
        // { 1:2, 2:1, 3:1, 4:1 } != { 1:3, 2:1, 3:1, 4:1 }
        var countsMissmatch = groupedSourceList.Any(sourceGroup =>
                                                        {
                                                            var targetGroup = groupedTargetList.Single(y => y.Key.Equals(sourceGroup.Key));
                                                            return sourceGroup.Count() != targetGroup.Count();
                                                        });
        return !countsMissmatch;
    }

0

Giải pháp đơn giản này buộc IEnumerableloại chung chung phải thực hiện IComparable. Vì OrderByđịnh nghĩa của.

Nếu bạn không muốn đưa ra một giả định như vậy nhưng vẫn muốn sử dụng giải pháp này, bạn có thể sử dụng đoạn mã sau:

bool equal = collection1.OrderBy(i => i?.GetHashCode())
   .SequenceEqual(collection2.OrderBy(i => i?.GetHashCode()));

0

Nếu so sánh cho mục đích của Xác nhận kiểm tra đơn vị, có thể có ý nghĩa để ném một số hiệu quả ra khỏi cửa sổ và chỉ cần chuyển đổi từng danh sách thành biểu diễn chuỗi (csv) trước khi thực hiện so sánh. Bằng cách đó, thông báo Xác nhận kiểm tra mặc định sẽ hiển thị sự khác biệt trong thông báo lỗi.

Sử dụng:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

Assert.Equal(collection1.OrderBy(c=>c).ToCsv(), collection2.OrderBy(c=>c).ToCsv());

Phương pháp mở rộng trợ giúp:

public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,
    string joinSeparator = ",")
{
    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) ||
            typeof(T) == typeof(Int32) ||
            typeof(T) == typeof(Int64))
        {
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}
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.