المقارنة بين مجموعتين من أجل المساواة بغض النظر عن ترتيب العناصر فيهما

StackOverflow https://stackoverflow.com/questions/50098

سؤال

أرغب في مقارنة مجموعتين (في C#)، لكنني لست متأكدًا من أفضل طريقة لتنفيذ ذلك بكفاءة.

لقد قرأت الموضوع الآخر عنه Enumerable.SequenceEqual, ، لكن هذا ليس بالضبط ما أبحث عنه.

في حالتي، مجموعتان ستكونان متساويتين إذا كانتا تحتويان على نفس العناصر (بغض النظر عن الترتيب).

مثال:

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

collection1 == collection2; // true

ما أفعله عادة هو التكرار خلال كل عنصر في مجموعة واحدة ومعرفة ما إذا كان موجودًا في المجموعة الأخرى، ثم التكرار عبر كل عنصر في المجموعة الأخرى ومعرفة ما إذا كان موجودًا في المجموعة الأولى.(أبدأ بمقارنة الأطوال).

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

ومع ذلك، هذا ليس صحيحًا تمامًا، وربما لا تكون الطريقة الأكثر فعالية لمقارنة مجموعتين لتحقيق المساواة.

أحد الأمثلة التي أعتقد أن ذلك سيكون خاطئًا هو:

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

والذي سيكون متساويًا مع تنفيذي.هل يجب أن أحسب عدد مرات العثور على كل عنصر والتأكد من تساوي الأعداد في كلتا المجموعتين؟


الأمثلة مكتوبة بنوع ما من C# (دعنا نسميها pseudo-C#)، ولكن أعط إجابتك بأي لغة تريدها، فلا يهم.

ملحوظة: لقد استخدمت الأعداد الصحيحة في الأمثلة من أجل البساطة، ولكني أريد أن أكون قادرًا على استخدام كائنات النوع المرجعي أيضًا (لا تتصرف بشكل صحيح كمفاتيح لأنه تتم مقارنة مرجع الكائن فقط، وليس المحتوى).

هل كانت مفيدة؟

المحلول

اتضح أن Microsoft قد قامت بالفعل بتغطية هذا الأمر في إطار الاختبار الخاص بها: CollectionAssert.AreEquivalent

ملاحظات

مجموعتان متكافئتان إذا كان لديهم نفس العناصر في نفس الكمية ، ولكن بأي ترتيب.العناصر متساوية إذا كانت قيمها متساوية ، وليس إذا كانت تشير إلى نفس الكائن.

باستخدام العاكس، قمت بتعديل الكود الموجود خلف AreEquivalent() لإنشاء مقارنة مساواة مقابلة.إنها أكثر اكتمالا من الإجابات الموجودة، لأنها تأخذ في الاعتبار القيم الخالية، وتطبق IEqualityComparer ولديها بعض عمليات التحقق من الكفاءة وحالة الحافة.بالإضافة إلى أنه مايكروسوفت :)

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

استخدام العينة:

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

أو إذا كنت تريد فقط مقارنة مجموعتين مباشرة:

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

أخيرًا، يمكنك استخدام مُقارن المساواة الذي تختاره:

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

نصائح أخرى

الحل البسيط والفعال إلى حد ما هو فرز كلتا المجموعتين ثم مقارنتهما لتحقيق المساواة:

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

هذه الخوارزمية هي O(N*logN)، بينما الحل أعلاه هو O(N^2).

إذا كانت المجموعات تحتوي على خصائص معينة، فقد تتمكن من تنفيذ حل أسرع.على سبيل المثال، إذا كانت مجموعتك عبارة عن مجموعات تجزئة، فلا يمكن أن تحتوي على نسخ مكررة.كما أن التحقق مما إذا كانت مجموعة التجزئة تحتوي على بعض العناصر يعد سريعًا جدًا.في هذه الحالة، من المرجح أن تكون الخوارزمية المشابهة لخوارزميتك هي الأسرع.

قم بإنشاء قاموس "dict" ثم لكل عضو في المجموعة الأولى، قم بإجراء dict[member]++;

ثم قم بالتكرار على المجموعة الثانية بنفس الطريقة، ولكن لكل عضو قم بإملاء[عضو]--.

في النهاية، قم بالتكرار على جميع الأعضاء في القاموس:

    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;

    }

يحرر:بقدر ما أستطيع أن أقول أن هذا بنفس ترتيب الخوارزمية الأكثر كفاءة.هذه الخوارزمية هي O(N)، على افتراض أن القاموس يستخدم عمليات البحث O(1).

هذا هو تطبيقي العام (المتأثر بشدة بـ D.Jennings) لطريقة المقارنة (في 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;
    }
}

يمكنك استخدام أ هاشت.انظر الى تعيين يساوي طريقة.

يحرر:أدركت بمجرد أن طرحت أن هذا لا يعمل إلا مع المجموعات - فهو لن يتعامل بشكل صحيح مع المجموعات التي تحتوي على عناصر مكررة.على سبيل المثال { 1, 1, 2 } و { 2, 2, 1 } سيتم اعتبارهما متساويين من منظور هذه الخوارزمية.إذا كانت مجموعاتك عبارة عن مجموعات (أو يمكن قياس المساواة بينها بهذه الطريقة)، آمل أن تجد ما يلي مفيدًا.

الحل الذي أستخدمه هو:

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

يقوم Linq بعمل القاموس تحت الأغطية، لذا فهو أيضًا O(N).(لاحظ أنه O(1) إذا لم تكن المجموعات بنفس الحجم).

لقد قمت بإجراء فحص سلامة العقل باستخدام طريقة "SetEqual" التي اقترحها دانيال، وطريقة OrderBy/SequenceEquals التي اقترحها Igor، واقتراحي.النتائج أدناه، تظهر O(N*LogN) لإيجور وO(N) لي ودانيال.

أعتقد أن بساطة كود التقاطع Linq تجعله الحل الأفضل.

__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

في حالة عدم التكرار وعدم وجود ترتيب، يمكن استخدام EqualityComparer التالي للسماح بالمجموعات كمفاتيح القاموس:

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

هنا هو تطبيق ToHashSet() الذي استخدمته.ال خوارزمية رمز التجزئة يأتي من Java الفعالة (عن طريق Jon Skeet).

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

يتطلب الحل .NET 3.5 و System.Collections.Generic مساحة الاسم. وفقا لمايكروسوفت, SymmetricExceptWith هو يا (ن + م) عملية، مع ن يمثل عدد العناصر في المجموعة الأولى و م يمثل عدد العناصر في الثانية.يمكنك دائمًا إضافة مُقارن المساواة إلى هذه الوظيفة إذا لزم الأمر.

لماذا لا تستخدم .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/library/bb397894.aspx

إذا كنت تستخدم ينبغي, ، يمكنك استخدام يجب أن يكون مع يحتوي على.

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

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

وأخيرًا، يمكنك كتابة امتداد.

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

تحديث

توجد معلمة اختيارية في يجب ان يكون طريقة.

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

وظيفة مكررة من نوع ما، ولكن تحقق من الحل الخاص بي لمقارنة المجموعات.الأمر بسيط جدًا:

سيؤدي هذا إلى إجراء مقارنة المساواة بغض النظر عن الترتيب:

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

سيؤدي هذا إلى التحقق لمعرفة ما إذا تمت إضافة/إزالة العناصر:

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

سيؤدي هذا إلى رؤية العناصر التي تغيرت في القاموس:

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

المشاركة الأصلية هنا.

إريكسون يكاد يكون على حق:نظرًا لأنك تريد مطابقة عدد التكرارات، فأنت تريد شنطة.في Java، يبدو هذا كالتالي:

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

أنا متأكد من أن C# يحتوي على تطبيق Set مدمج.سأستخدم ذلك أولاً؛إذا كان الأداء يمثل مشكلة، فيمكنك دائمًا استخدام تطبيق Set مختلف، ولكن استخدم نفس واجهة Set.

إليك طريقة التمديد الخاصة بي لإجابة ohadsc، في حال كانت مفيدة لشخص ما

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

هنا هو الحل الذي هو تحسين أكثر هذا.

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

هناك العديد من الحلول لهذه المشكلة.إذا كنت لا تهتم بالتكرارات، فلن يتعين عليك فرز كليهما.تأكد أولاً من أن لديهم نفس عدد العناصر.بعد ذلك قم بفرز إحدى المجموعات.ثم قم بالبحث في كل عنصر من المجموعة الثانية في المجموعة التي تم فرزها.إذا لم تجد عنصرًا معينًا، توقف وأرجع خطأ.تعقيد هذا:- فرز المجموعة الأولى:نسجل (ن) - البحث في كل عنصر من الثانية إلى الأول:نسجل (n) حتى ينتهي بك الأمر بـ 2*n*log (n) على افتراض أنها تتطابق وأنك تبحث عن كل شيء.وهذا مشابه لتعقيد الفرز على حد سواء.يمنحك هذا أيضًا فائدة التوقف مبكرًا إذا كان هناك فرق.ومع ذلك، ضع في اعتبارك أنه إذا تم فرز كليهما قبل الدخول في هذه المقارنة وحاولت الفرز باستخدام شيء مثل qsort، فسيكون الفرز أكثر تكلفة.هناك تحسينات لهذا.البديل الآخر، والذي يعد رائعًا للمجموعات الصغيرة حيث تعرف نطاق العناصر، هو استخدام فهرس قناع نقطي.سيعطيك هذا أداء O(n).بديل آخر هو استخدام التجزئة والبحث عنه.بالنسبة للمجموعات الصغيرة، عادةً ما يكون من الأفضل إجراء الفرز أو فهرس القناع النقطي.لدى Hashtable مساوئ المنطقة المحلية الأسوأ لذا ضع ذلك في الاعتبار.مرة أخرى، هذا فقط إذا كنت لا تهتم بالنسخ المكررة.إذا كنت تريد حساب التكرارات، فاتبع فرز كليهما.

في كثير من الحالات، الإجابة الوحيدة المناسبة هي إجابة إيجور أوستروفسكي، وتعتمد الإجابات الأخرى على رمز تجزئة الكائنات.ولكن عندما تقوم بإنشاء رمز تجزئة لكائن ما، فإنك تفعل ذلك فقط بناءً على حقوله غير القابلة للتغيير - مثل حقل معرف الكائن (في حالة كيان قاعدة البيانات) -لماذا من المهم تجاوز GetHashCode عند تجاوز طريقة Equals؟

وهذا يعني أنه إذا قمت بمقارنة مجموعتين، فقد تكون النتيجة صحيحة بالنسبة لطريقة المقارنة على الرغم من أن حقول العناصر المختلفة غير متساوية.لإجراء مقارنة عميقة للمجموعات، تحتاج إلى استخدام طريقة Igor وتنفيذ IEqualirity.

يرجى قراءة تعليقاتي وتعليقات السيد شنايدر على مشاركاته الأكثر تصويتًا.

جوامع

السماح بالتكرارات في IEnumerable<T> (إذا كانت المجموعات غير مرغوب فيها/ممكنة) و"تجاهل الطلب" فيجب أن تكون قادرًا على استخدام ملف .GroupBy().

أنا لست خبيرًا في قياسات التعقيد، لكن فهمي الأولي هو أن هذا يجب أن يكون O(n).أفهم أن O(n^2) يأتي من إجراء عملية O(n) داخل عملية O(n) أخرى مثل ListA.Where(a => ListB.Contains(a)).ToList().يتم تقييم كل عنصر في ListB للمساواة مع كل عنصر في ListA.

كما قلت، فهمي للتعقيد محدود، لذا صححوني إذا كنت مخطئًا.

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

هذا الحل البسيط يجبر IEnumerableالنوع العام المطلوب تنفيذه IComparable.بسببOrderByتعريف.

إذا كنت لا تريد إجراء مثل هذا الافتراض ولكنك لا تزال ترغب في استخدام هذا الحل، فيمكنك استخدام الكود التالي:

bool equal = collection1.OrderBy(i => i?.GetHashCode())
   .SequenceEqual(collection2.OrderBy(i => i?.GetHashCode()));
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top