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

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

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

16 ответов

Решение

Enumerable.SequenceEqual

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

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

Как и другие предлагали и отмечали, 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))

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

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

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

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

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

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

Взгляните на метод 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);

.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

Это не является прямым ответом на ваши вопросы, но MS TestTools и NUnit предоставляют

 CollectionAssert.AreEquivalent

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

Я не знал о методе 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 создала функцию, которую я описал для вас.

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

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

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

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

Для упорядоченных коллекций (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>)

Как насчет этого примера:

 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

Я сделал свой метод сравнения. Он возвращает общие, отсутствующие и дополнительные значения.

private static void Compare<T>(IEnumerable<T> actual, IEnumerable<T> expected, out IList<T> common, out IList<T> missing, out IList<T> extra) {
    common = new List<T>();
    missing = new List<T>();
    extra = new List<T>();

    var expected_ = new LinkedList<T>( expected );
    foreach (var item in actual) {
        if (expected_.Remove( item )) {
            common.Add( item );
        } else {
            extra.Add( item );
        }
    }
    foreach (var item in expected_) {
        missing.Add( item );
    }
}
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;
}

Не было, нет и не может быть, по крайней мере, я бы поверил в это. Причина заключается в том, что равенство коллекций, вероятно, определяется пользователем.

Элементы в коллекциях не должны быть в определенном порядке, хотя они имеют естественный порядок, это не то, на что должны опираться алгоритмы сравнения. Скажем, у вас есть две коллекции:

{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

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

Нет, потому что фреймворк не знает, как сравнивать содержимое ваших списков.

Посмотри на это:

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

Сравнение содержимого словарей:

Сравнить два Dictionary<K, V>объекты, мы можем предположить, что ключи уникальны для каждого значения, таким образом, если два набора ключей равны, то и содержимое двух словарей одинаково.

      Dictionary<K, V> dictionaryA, dictionaryB;
bool areDictionaryContentsEqual = new HashSet<K>(dictionaryA.Keys).SetEquals(dictionaryB.Keys);

Сравнение содержимого коллекций:

Сравнить два ICollection<T>объекты, нам нужно проверить:

  1. Если они одинаковой длины.
  2. Если каждый Tзначение, которое появляется в первой коллекции, появляется такое же количество раз во второй.
      public static bool AreCollectionContentsEqual<T>(ICollection<T> collectionA, ICollection<T> collectionB)
    where T : notnull
{
    if (collectionA.Count != collectionB.Count)
    {
        return false;
    }
    Dictionary<T, int> countByValueDictionary = new(collectionA.Count);
    foreach(T item in collectionA)
    {
        countByValueDictionary[item] = countByValueDictionary.TryGetValue(item, out int count) 
            ? count + 1 
            : 1;
    }
    foreach (T item in collectionB)
    {
        if (!countByValueDictionary.TryGetValue(item, out int count) || count < 1)
        {
            return false;
        }
        countByValueDictionary[item] = count - 1;
    }
    return true;
}

Эти решения должны быть оптимальными, поскольку их сложность времени и памяти равна O(n).

Нет. В структуре коллекции нет понятия равенства. Если вы думаете об этом, нет способа сравнить коллекции, которые не являются субъективными. Например, сравнивая ваш IList с вашим словарем, будут ли они равны, если все ключи были в IList, все значения были в IList или оба были в IList? Не существует очевидного способа сравнения этих двух коллекций без знания того, для чего они будут использоваться, поэтому метод равенства общего назначения не имеет смысла.

Другие вопросы по тегам