Порівняння двох збірників для рівності незалежно від порядку предметів у них


162

Я хотів би порівняти дві колекції (в 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 # (назвемо це псевдо-C #), але давати свою відповідь будь-якою мовою ви хочете, це не має значення.

Примітка. Я використовував цілі числа в прикладах для простоти, але хочу також використовувати об'єкти опорного типу (вони не ведуть себе правильно як ключі, оскільки порівнюються лише посилання на об'єкт, а не вміст).


1
Як щодо алгоритму? Всі відповіді пов'язані з порівнянням чогось, загальними списками порівнюють linq і т. Д. Дійсно чи ми обіцяли комусь, що ніколи не будемо використовувати алгоритм як старомодний програміст?
Нурі ЙІЛМАЗ

Ви не перевіряєте рівність, ви перевіряєте рівність. Це нитко, але важлива відмінність. І давно. Це хороший Q + A.
CAD заблокували

Вас може зацікавити ця публікація , в якій обговорюється налаштована версія описаного нижче словника. Одне питання з найбільш простими підходами до словника полягає в тому, що вони не обробляють нулі належним чином, оскільки клас .NET Dictionary не дозволяє нульові ключі.
ChaseMedallion

Відповіді:


112

Виявляється, Microsoft вже охоплює це у своїх тестових рамках: CollectionAssert.AreEquivalent

Зауваження

Дві колекції рівноцінні, якщо вони мають однакові елементи в тій же кількості, але в будь-якому порядку. Елементи рівні, якщо їх значення рівні, а не якщо вони посилаються на один і той же об'єкт.

Використовуючи рефлектор, я змінив код за AreEquivalent (), щоб створити відповідний порівняльник рівності. Він є більш повним, ніж існуючі відповіді, оскільки враховує нулі, впроваджує IEqualityComparer та має деякі перевірки ефективності та кращих випадків. плюс, це 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;
    }
}

Використання зразка:

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

7
Я не впевнений на 100%, але думаю, що ваша відповідь порушує умови використання Microsoft проти зворотної інженерії.
Ієн Даллас

1
Здрастуйте, Охад, прочитайте наступну тривалу дискусію в темі, stackoverflow.com/questions/371328/… Якщо ви зміните хеш-код об’єкта, тоді як його в хеш-сеті він перерве правильну дію хештету і може спричинити виняток. Правило таке: Якщо два об'єкти рівні - вони повинні мати однаковий хеш-код. Якщо два об'єкти мають однаковий хеш-код - для них це не обов'язково, щоб вони були рівними. Хеш-код повинен залишатися однаковим протягом усього життя об'єкта! Ось чому ви спонукаєте ICompareable та IEqualrity.
James Roeiter

2
@JamesRoeiter Можливо, мій коментар був оманливим. Коли у словнику зустрічається хеш-код, який він уже містить, він перевіряє фактичну рівність з EqualityComparer(або тим, що ви надали, або EqualityComparer.Defaultви можете перевірити Reflector або опорне джерело, щоб перевірити це). Щоправда, якщо об'єкти змінюються (а саме їхні хеш-коди змінюються) під час роботи цього методу, результати є несподіваними, але це означає, що цей метод не є безпечним для потоків у цьому контексті.
Охад Шнайдер

1
@JamesRoeiter Припустимо, x і y - два об'єкти, які ми хочемо порівняти. Якщо вони мають різні хеш-коди, ми знаємо, що вони різні (тому що рівні елементи мають рівні хеш-коди), і вищевказана реалізація є правильною. Якщо вони мають однаковий хеш-код, реалізація словника перевірить фактичну рівність, використовуючи вказаний EqualityComparer(або EqualityComparer.Defaultякщо його не вказано), і знову реалізація правильна.
Охад Шнайдер

1
@CADbloke метод повинен бути названий Equalsчерез IEqualityComparer<T>інтерфейс. На що слід звернути увагу, - це ім’я самого порівняльника . У цьому випадку це MultiSetComparerмає сенс.
Охад Шнайдер

98

Просте і досить ефективне рішення - сортувати обидві колекції, а потім порівняти їх для рівності:

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

Цей алгоритм - O (N * logN), тоді як ваше рішення вище - O (N ^ 2).

Якщо колекції мають певні властивості, можливо, ви зможете реалізувати швидше рішення. Наприклад, якщо обидві ваші колекції є хеш-наборами, вони не можуть містити дублікатів. Також дуже швидко перевірити, чи містить хеш якийсь елемент. У такому випадку алгоритм, подібний до вашого, мабуть, буде найшвидшим.


1
Вам просто потрібно додати використовуючи System.Linq; Перший, щоб він працював
Джуніор Мейх

якщо цей код знаходиться в циклі, і collection1 оновлюється, а collection2 залишається недоторканою, зауважте, навіть коли обидві колекції мають один і той же об'єкт, налагоджувач буде показувати помилково для цієї "рівної" змінної.
Молодший Мейхе

5
@Chaulky - Я вважаю, що OrderBy потрібен. Дивіться: dotnetfiddle.net/jA8iwE
Бретт

Яка інша відповідь називалася "вище"? Можливо, stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs

32

Створіть словник "dict", а потім для кожного члена в першій колекції зробіть dict [member] ++;

Потім, переведіть на другу колекцію таким же чином, але для кожного члена зробіть диктування [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).


Це майже те, що я хочу. Однак я хотів би це зробити, навіть якщо я не використовую цілі числа. Я хотів би використовувати довідкові об’єкти, але вони не так поводяться як ключі в словниках.
mbillard

Моно, ваше запитання суперечить, якщо ваші предмети не порівнянні. Якщо їх не можна використовувати як ключі у словнику, рішення не доступне.
Сколіма

1
Я думаю, що Моно означав, що ключі не можна сортувати. Але рішення Даніеля, очевидно, призначене для впровадження з хешбелом, а не деревом, і воно буде працювати, поки є тест на еквівалентність і хеш-функція.
erickson

Звісно, ​​що за допомогу, але не прийнято, оскільки в ній відсутній важливий момент (який я висвітлюю у своїй відповіді).
mbillard

1
FWIW, ви можете спростити свій останній цикл foreach і повернути заяву з цим:return dict.All(kvp => kvp.Value == 0);
Tyson Williams

18

Це моя (під сильним впливом Д. Дженінгса) загальна реалізація методу порівняння (в 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
Хороша робота, але зауважте: 1. На відміну від рішення Деніела Дженнінгса, це не O (N), а скоріше O (N ^ 2) через функцію знаходження всередині петлі foreach на колекції смужок; 2. Ви можете узагальнити метод прийняття IEnumerable <T> замість ICollection <T> без подальшої модифікації коду
Охад Шнайдер

The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"- це не правда. Алгоритм заснований на помилкових припущеннях і, хоча працює, він надзвичайно неефективний.
Антонін Лейсек


7

Якщо ви використовуєте Shouldly , ви можете використовувати ShouldAllBe з вмістом.

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

ОНОВЛЕННЯ

Необов’язковий параметр існує у методі ShouldBe .

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

1
Я тільки що знайшов на останню версію , що є параметр bool ignoreOrderна ShouldBe методі.
Пір-Ліонель Сгард

5

EDIT: Як тільки я поставив думку, я зрозумів, що це справді працює лише для наборів - це не буде належним чином мати справу з колекціями, що містять повторювані елементи. Наприклад, {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, запропонований Ігорем, і мою пропозицію. Результати наведені нижче, показуючи 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

Єдине питання цього коду полягає в тому, що він працює лише при порівнянні типів значень або порівнянні покажчиків із типовими типами. Я міг би мати два різних екземпляри одного і того ж об'єкта в колекціях, тому мені потрібно мати можливість вказати, як порівнювати кожен. Чи можете ви передати делегата порівняння методу перетину?
mbillard

Звичайно, ви можете пройти делегата порівняння. Але зауважте вищенаведене обмеження щодо наборів, які я додав, що ставить суттєве обмеження щодо його застосовності.

Метод Intersect повертає виразну колекцію. Дано a = {1,1,2} і b = {2,2,1}, a.Intersect (b) .Count ()! = A.Count, що призводить до того, що вираз правильно поверне помилкове значення. {1,2} .Count! = {1,1,2} .Count Див. Посилання [/ link] (Зверніть увагу, що обидві сторони перед порівнянням виокремлюються.)
Griffin

5

У разі повторень і жодних замовлень, наступний 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 (шляхом Джона Скіта).


У чому сенс серіалізабельності для класу Порівняння? : o Також ви можете змінити вхід, щоб ISet<T>виразити, що він призначений для наборів (тобто немає дублікатів).
nawfal

@nawfal спасибі, не знаю, про що я думав, коли я відзначив це серіалізацією ... Щодо того ISet, ідея тут полягала в тому, щоб ставитися до IEnumerableнабору (тому що у вас IEnumerableпочаток з цього), хоча з огляду на 0 оновлених результатів більше 5 років, які, можливо, не були найгострішою ідеєю: П
Охад Шнайдер

4
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простір імен. На думку Microsoft , SymmetricExceptWithце операція O (n + m) , при якій n представляє кількість елементів у першому наборі, а m являє собою кількість елементів у другому. Ви завжди можете додати порівнянність рівності до цієї функції, якщо це необхідно.


3

Чому б не використовувати .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


2
Exceptне працюватиме для підрахунку повторюваних елементів. Він повернеться істинним для множин {1,2,2} і {1,1,2}.
Крістіан Діаконеску

@ CristiDiaconescu ви могли б спочатку зробити ".Distinct ()", щоб видалити всі дублікати
Korayem

ОП просить [1, 1, 2] != [1, 2, 2]. Використання Distinctдозволило б їм виглядати рівними.
Крістіан Діаконеску

2

Дублікат публікацій сортів, але ознайомтесь із моїм рішенням щодо порівняння колекцій . Це досить просто:

Це здійснить порівняння рівності незалежно від порядку:

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

Оригінальний пост тут .


1

erickson майже правий: оскільки ви хочете збігатися за кількістю дублікатів, ви хочете Сумка . У Java це виглядає приблизно так:

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

Я впевнений, що у C # є вбудована програма Set. Я би скористався цим першим; якщо продуктивність є проблемою, ви завжди можете використовувати іншу програму Set Set, але використовувати той же інтерфейс Set.


1

Ось мій варіант розширення методу відповіді 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;
    }
}

Наскільки добре це виконує якісь ідеї?
nawfal

Я використовую це лише для невеликих колекцій, тому не замислювався над складністю Big-O і не робив тестування. Один лише параметр HaveMismatchedElements є O (M * N), тому він може не працювати у великих колекціях.
Ерік Дж.

Якщо IEnumerable<T>s - це запити, то дзвінки Count()не є гарною ідеєю. Підхід оригінальної відповіді Охада перевірити, чи є вони - ICollection<T>це краща ідея.
nawfal

1

Ось рішення, яке є покращенням над цим .

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

Існує багато рішень цієї проблеми. Якщо ви не дбаєте про дублікати, вам не доведеться сортувати обидва. Спочатку переконайтеся, що вони мають однакову кількість предметів. Після цього сортуйте одну з колекцій. Потім перегляньте кожен елемент з другої колекції у відсортованій колекції. Якщо ви не знайдете заданий елемент, зупиніться і поверніть помилкове. Складність цього: - сортування першої колекції: N Журнал (N) - пошук кожного елемента з другого до першого: NLOG (N), тож ви закінчуєтесь 2 * N * LOG (N), припускаючи, що вони відповідають, і ви все шукаєте. Це схоже на складність сортування обох. Також це дає перевагу зупинитися раніше, якщо є різниця. Однак майте на увазі, що якщо обидва відсортовані перед тим, як перейти до цього порівняння, і ви спробуєте сортувати, використовуючи щось на зразок qsort, сортування буде дорожчим. Для цього є оптимізації. Ще одна альтернатива, яка чудово підходить для невеликих колекцій, де ви знаєте коло елементів, - використовувати індекс біткових масок. Це дасть вам O (n) продуктивність. Ще одна альтернатива - використовувати хеш і шукати його. Для невеликих колекцій зазвичай набагато краще зробити сортування або індекс біткових масок. Недоліком хешбела є гірший населений пункт, тому пам’ятайте про це. Знову ж таки, це лише якщо ви не ' t піклуватися про дублікати. Якщо ви хочете обліковувати дублікати, перейдіть до сортування обох.


0

У багатьох випадках єдина відповідь - Ігор Островський, інші відповіді - на основі хеш-коду об'єктів. Але коли ви генеруєте хеш-код для об’єкта, ви робите це лише на основі його НЕМАТИЧНИХ полів - таких як поле Id об’єкта (у випадку сутності бази даних) - Чому важливо переосмислити GetHashCode, коли метод «рівний» буде замінено?

Це означає, що якщо порівнювати дві колекції, результат може бути істинним щодо методу порівняння, навіть якщо поля різних елементів не рівні. Для глибокого порівняння колекцій потрібно використовувати метод Ігоря та реалізувати IEqualirity.

Будь ласка, прочитайте коментарі мене та м.Шнідера до його найбільш голосового повідомлення.

Джеймс


0

Дозволяючи дублікати у 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;
    }

0

Це просте рішення змушує IEnumerableреалізувати родовий тип IComparable. Через OrderByвизначення Росії.

Якщо ви не хочете робити таке припущення, але все ж хочете скористатися цим рішенням, ви можете скористатися таким кодом:

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

0

Якщо порівнювати цілі тверджень одиничного тестування, можливо, має сенс викинути деяку ефективність у вікно і просто перетворити кожен список у представлення рядків (csv), перш ніж проводити порівняння. Таким чином, повідомлення про тест за твердженням за замовчуванням відобразить відмінності в повідомленні про помилку.

Використання:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

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

Спосіб розширення допомоги:

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)));
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.