Есть ли встроенный метод для сравнения коллекций?
Я хотел бы сравнить содержимое нескольких коллекций в моем методе 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-сервер предлагает вашему вниманию, когда вы пытаетесь сделать нумерацию страниц, вам нужно предоставить правила сортировки:
Еще две коллекции:
{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>
объекты, нам нужно проверить:
- Если они одинаковой длины.
- Если каждый
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? Не существует очевидного способа сравнения этих двух коллекций без знания того, для чего они будут использоваться, поэтому метод равенства общего назначения не имеет смысла.