Есть ли встроенный метод для сравнения коллекций?

178

Я хотел бы сравнить содержимое нескольких коллекций в моем методе Equals. У меня есть словарь и IList. Есть ли встроенный метод для этого?

Отредактировано: я хочу сравнить два словаря и два ILists, поэтому я думаю, что означает равенство, ясно - если два словаря содержат одинаковые ключи, сопоставленные с одинаковыми значениями, то они равны.

TimK
источник
Независимо от порядка или нет IList? Вопрос неоднозначный.
nawfal
Enumerable.SequenceEqualи ISet.SetEqualsпредоставить версии этой функциональности. Если вы хотите быть независимыми от заказов и работать с коллекциями, в которых есть дубликаты, вам нужно создать свою собственную. Проверьте реализацию, предложенную в этом посте
ChaseMedallion

Ответы:

185

Enumerable.SequenceEqual

Определяет, равны ли две последовательности, сравнивая их элементы, используя указанный IEqualityComparer (T).

Вы не можете напрямую сравнить список и словарь, но вы можете сравнить список значений из словаря со списком

Гленн Славен
источник
52
Проблема в том, что SequenceEqual ожидает, что элементы будут в том же порядке. Класс Dictionary не гарантирует порядок ключей или значений при перечислении, поэтому, если вы собираетесь использовать SequenceEqual, сначала нужно отсортировать .Keys и .Values!
Орион Эдвардс
3
@ Орион: ... если, конечно, вы не хотите обнаруживать различия в порядке
следования
30
@schoetbi: Почему вы хотите обнаружить различия в заказе в контейнере, который не гарантирует заказ ?
Матти Вирккунен
4
@schoetbi: Это для того, чтобы вынуть определенный элемент из IEnumerable. Однако словарь не гарантирует порядок, так .Keysи .Valuesможет вернуть ключи и значения в любом порядке , они чувствуют , как и этот порядок может измениться , как словарь модифицируется , а также. Я предлагаю вам прочитать, что такое словарь, а что нет.
Матти Вирккунен,
5
MS 'TestTools и NUnit предоставляют CollectionAssert.AreEquivalent
тымтам
44

Как и предполагали другие, SequenceEqualчувствителен к порядку. Чтобы решить эту проблему, вы можете отсортировать словарь по ключу (который уникален и, следовательно, сортировка всегда стабильна), а затем использовать SequenceEqual. Следующее выражение проверяет, равны ли два словаря независимо от их внутреннего порядка:

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

РЕДАКТИРОВАТЬ: Как указал Джепп Стиг Нильсен, некоторые объекты имеют 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

В дополнение к упомянутому SequenceEqual , который

Истинно, если два списка имеют одинаковую длину и их соответствующие элементы сравниваются равными в соответствии с компаратором

(который может быть компаратором по умолчанию, то есть переопределением Equals())

Стоит отметить, что в .Net4 есть SetEquals для ISetобъектов, которые

игнорирует порядок элементов и любых повторяющихся элементов.

Поэтому, если вы хотите иметь список объектов, но они не обязательно должны быть в определенном порядке, подумайте, что ISet(например, a HashSet) может быть правильным выбором.

Desty
источник
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] и ваш тест провалится
Орион Эдвардс
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
user329244
источник
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} не вернет ничего добавленного / удаленного.
Найл Коннотон
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

который делает в значительной степени то, что вы хотите.

tymtam
источник
Искал это для моего теста NUnit
Blem
1

Для сравнения коллекций вы также можете использовать LINQ. Enumerable.Intersectвозвращает все пары, которые равны Вы можете сравнить два словаря, как это:

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

Первое сравнение необходимо, потому что dict2может содержать все ключи dict1и многое другое.

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

Chrono
источник
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

IsPostBack
источник
value! = pair.Value выполняет сравнение ссылок, вместо этого используйте Equals
kofifus
1

Для упорядоченных коллекций (List, Array) используйте 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>)

kofifus
источник
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;
}
mbadeveloper
источник
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это достаточно хорошо, но возвращает false в следующем случае:

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

  • Если ссылки равны, это просто Борис

Кен Кин
источник