Чи існує вбудований метод порівняння колекцій?


178

Я хотів би порівняти вміст пари колекцій у моєму методі рівних. У мене є Словник та Ілліст. Чи є вбудований метод для цього?

Відредаговано: Я хочу порівняти два словники та два ILIST, тому я думаю, що означає рівність зрозуміло - якщо два словники містять однакові ключі, відображені на однакові значення, то вони рівні.


Незалежно від замовлення чи ні IList? Питання неоднозначне.
nawfal

Enumerable.SequenceEqualі ISet.SetEqualsнадати версії цього функціоналу. Якщо ви хочете бути замовником і працювати з колекціями, що мають дублікати, вам потрібно буде прокрутити свій власний. Ознайомтесь із запропонованою у цій публікації реалізацією
ChaseMedallion

Відповіді:


185

Enumerable.SequenceEqual

Визначає, чи дві послідовності рівні, порівнюючи їх елементи, використовуючи заданий IEqualityComparer (T).

Ви не можете безпосередньо порівняти список та словник, але ви можете порівняти список значень зі Словника зі списком


52
Проблема полягає в тому, що SequenceEqual очікує, що елементи будуть в одному порядку. Клас Словник не гарантує порядок ключів або значень при перерахуванні, тому, якщо ви збираєтесь використовувати SequenceEqual, вам слід спочатку сортувати .Keys та .Values!
Оріон Едвардс

3
@Orion: ... якщо ви не хочете виявити різниці в порядку замовлення, звичайно :-)
schoetbi

30
@schoetbi: Чому ви хочете виявити відмінності в порядку замовлення в контейнері, який не гарантує замовлення ?
Матті Віркюнен

4
@schoetbi: Це для того, щоб вийняти певний елемент із безлічі. Однак словник не гарантує порядок, так .Keysі .Valuesможе повернути ключі і значення в будь-якому порядку , вони відчувають , як і цей порядок може змінитися , як словник модифікується , а також. Я пропоную вам прочитати, що таке Словник, а що - ні.
Матті Вірккунен

5
MS 'TestTools і NUnit надають CollectionAssert.AreEquivalent
tymtam

44

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

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

EDIT: Як вказував Джеппе Стіг Нільсен, деякі об'єкти мають той, IComparer<T>що несумісний з їхніми IEqualityComparer<T>, даючи неправильні результати. Використовуючи ключі з таким об’єктом, ви повинні вказати правильне IComparer<T>для цих клавіш. Наприклад, за допомогою рядкових клавіш (які демонструють цю проблему) ви повинні зробити наступне, щоб отримати правильні результати:

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

Що робити, якщо тип ключа не буде CompareTo? Тоді ваше рішення вибухне. Що робити, якщо тип ключа має порівняльник за замовчуванням, який не сумісний з його порівнянням рівності за замовчуванням? Це справа string, ви знаєте. Як приклад, ці словники (із неявними порівняльниками рівності за замовчуванням) не зможуть перевірити тест (за всіма відомостями про мене, про які я знаю):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: Щодо несумісності між IComparerі IEqualityComparer- я не знав цього питання, дуже цікаво! Я оновив відповідь можливим рішенням. Щодо нестачі CompareTo, я думаю, що розробник повинен переконатися, що делегат, наданий OrderBy()методу, повертає щось порівнянне. Я думаю, що це справедливо для будь-якого використання або OrderBy()навіть поза словниковим порівнянням.
Аллон Гуралнек

15

Крім згаданої послідовностіEqual , яка

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

(який може бути порівняльним за замовчуванням, тобто переосмисленим Equals())

варто згадати, що в .Net4 є SetEquals на ISetоб'єктах, який

ігнорує порядок елементів та будь-які повторювані елементи.

Отже, якщо ви хочете мати список об’єктів, але вони не повинні бути в певному порядку, врахуйте, що ISet(на зразок а HashSet) може бути правильним вибором.


7

Погляньте на метод Enumerable.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
Це не є надійним, оскільки SequenceEqual очікує, що значення зі словника вийдуть у надійному порядку - словник не дає таких гарантій щодо порядку, а словник. Ключі цілком можуть вийти як [2, 1] замість [1, 2] і ваш тест не вдасться
Orion Edwards

5

.NET Не вистачає потужних інструментів для порівняння колекцій. Я розробив просте рішення, яке ви можете знайти за посиланням нижче:

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

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

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

10
Звичайно. .NET має потужні інструменти для порівняння колекцій (це набір операцій). .Removedте саме list1.Except(list2), що .Addedє list2.Except(list1), .Equalє list1.Intersect(list2)і .Differentє original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Ви можете зробити майже будь-яке порівняння з LINQ.
Аллон Гуралнек

3
Виправлення: .Differentє original.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). І ви навіть можете додати, OldValue = left.Valueякщо вам потрібно і старе значення.
Аллон Гуралнек

3
@AllonGuralnek ваші пропозиції хороші, але вони не обробляють випадок, коли Список не є істинним набором - де список містить один і той же об'єкт кілька разів. Якщо порівнювати {1, 2} і {1, 2, 2}, нічого не буде додано / видалено.
Niall Connaughton


4

Я не знав про метод Enumerable.SequenceEqual (ви щодня дізнаєтесь щось ....), але я збирався запропонувати використовувати метод розширення; щось на зразок цього:

    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;

    }

Цікаво, що після проходження 2 секунд, щоб прочитати про SequenceEqual, схоже, Microsoft створив функцію, яку я описав для вас.


4

Це не відповідає безпосередньо на ваші запитання, але і MS 'TestTools, і NUnit пропонують

 CollectionAssert.AreEquivalent

що робить майже все, що ви хочете.


Шукав це для мого тесту на NUnit
Blem,

1

Для порівняння колекцій ви також можете використовувати LINQ. Enumerable.Intersectповертає всі рівні пари. Ви можете порівняти два словники на кшталт цього:

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

Перше порівняння потрібне, оскільки dict2може містити всі клавіші від dict1і більше.

Ви можете також використовувати думати про варіанти з використанням Enumerable.Exceptі Enumerable.Unionякі призводять до подібних результатів. Але можна використовувати для визначення точних відмінностей між множинами.


1

Як щодо цього прикладу:

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

Люб’язно: https://www.dotnetperls.com/dictionary-equals


value! = pair.Value робить порівняльне порівняння, замість цього використовуйте
рівняння

1

Для замовлених колекцій (Список, Масив) використовуйте SequenceEqual

для використання HashSet SetEquals

Для словника ви можете:

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

(Більш оптимізоване рішення використовуватиме сортування, але це вимагатиме IComparable<TValue>)


0

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


0

Ні, оскільки система не знає, як порівнювати вміст списків.

Погляньте на це:

http://blogs.msdn.com/abhinaba/archive/2005/10/11/479537.aspx


3
Хіба вже не існує декількох варіантів, щоб сказати рамці, як порівнювати елементи? IComparer<T>, Перекриваючи object.Equals, IEquatable<T>, IComparable<T>...
Стефан Steinegger

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

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

Елементи в колекціях не повинні бути в певному порядку, хоча вони мають впорядкування природно, це не те, на що слід покладатися алгоритми порівняння. Скажімо, у вас є дві колекції:

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

Вони рівні чи ні? Ви повинні знати, але я не знаю, яка ваша точка зору.

Колекції концептуально не упорядковані за замовчуванням, поки алгоритми не надають правила сортування. Те саме, що зверне вашу увагу на SQL-сервер, коли ви намагаєтеся зробити пагінацію, він вимагає надати правила сортування:

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

Ще дві колекції:

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

Знову вони рівні чи ні? Ти говориш мені ..

Dictionary<TKey, TValue>Повторність елементів колекції відіграє свою роль у різних сценаріях, а деякі колекції, як-от, навіть не дозволяють повторювати елементи.

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

Ну, в загальних випадках Enumerable.SequenceEqualце досить добре, але він повертає помилку в наступному випадку:

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

Я прочитав кілька відповідей на подібні питання (ви можете їм google ) і що я б використовував, взагалі:

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

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

  • GetHashCode()просто для впорядкування не для рівності; Я думаю, що цього в цьому достатньо

  • Count() насправді не перераховує колекцію та безпосередньо потрапляє у власність реалізації ICollection<T>.Count

  • Якщо посилання рівні, це просто Борис

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