С# Сравните два словаря для равенства
Я хочу сравнить в двух словарях С# с ключами a string
и как значение список int
s. Я предполагаю, что два словаря равны, если оба они имеют одинаковые ключи, а для каждого ключа - значение с одним и тем же целым числом (оба необязательно в одном порядке).
Я использую оба ответа из этого и этого связанного с этим вопроса, но оба отказали в моем тестовом наборе для тестовые функции DoesOrderKeysMatter
и DoesOrderValuesMatter
.
Моя тестовая версия:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
namespace UnitTestProject1
{
[TestClass]
public class ProvideReportTests
{
[TestMethod]
public void AreSameDictionariesEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict1);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
[TestMethod]
public void AreDifferentDictionariesNotEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
// act
bool dictsAreEqual = true;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsFalse(dictsAreEqual, "Dictionaries are equal");
}
[TestMethod]
public void DoesOrderKeysMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(3);
list3.Add(4);
dict2.Add("b", list3);
List<int> list4 = new List<int>();
list4.Add(1);
list4.Add(2);
dict2.Add("a", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
[TestMethod]
public void DoesOrderValuesMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(2);
list3.Add(1);
dict2.Add("a", list3);
List<int> list4 = new List<int>();
list4.Add(4);
list4.Add(3);
dict2.Add("b", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
return dict1.Keys.Count == dict2.Keys.Count &&
dict1.Keys.All(k => dict2.ContainsKey(k) && object.Equals(dict2[k], dict1[k]));
// also fails:
// return dict1.OrderBy(kvp => kvp.Key).SequenceEqual(dict2.OrderBy(kvp => kvp.Key));
}
}
}
Каков правильный способ сравнения этих словарей? Или есть ошибка в моем (по общему признанию неуклюже написанном) TestSuite?
Обновление
Я пытаюсь включить ответ Servy в свой тестовый пакет, как показано ниже, но я получаю некоторые ошибки (подчеркнутые красной вигглой в Visual Studio):
-
SetEquals
в `Equals method says:" не содержит определения для SetEquals, принимающего первый аргумент типа Generic.List.
-
В AreDictionariesEqual it says
DictionaryComparer > является типом, но используется как переменная.
пространство имен UnitTestProject1
{ [TestClass] публичный класс ProvideReportTests { [Метод испытания] //... так же, как указано выше
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
DictionaryComparer<string, List<int>>(new ListComparer<int>() dc = new DictionaryComparer<string, List<int>>(new ListComparer<int>();
return dc.Equals(dict1, dict2);
}
}
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
{
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
}
public int GetHashCode(Dictionary<TKey, TValue> obj)
{
throw new NotImplementedException();
}
}
public class ListComparer<T> : IEqualityComparer<List<T>>
{
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
}
public bool Equals(List<T> x, List<T> y)
{
return x.SetEquals(y, valueComparer);
}
public int GetHashCode(List<T> obj)
{
throw new NotImplementedException();
}
}
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer)
{
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
}
}
Ответы
Ответ 1
Итак, сначала нам нужен сопоставитель равенства для словарей. Он должен убедиться, что у них есть соответствующие ключи, и если они это сделают, сравните значения каждого ключа:
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
{
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
}
public int GetHashCode(Dictionary<TKey, TValue> obj)
{
throw new NotImplementedException();
}
}
но этого недостаточно. Нам нужно сравнить значения словаря с помощью другого настраиваемого компаратора, а не сравнения по умолчанию, поскольку сопоставитель по умолчанию не будет смотреть на значения списка:
public class ListComparer<T> : IEqualityComparer<List<T>>
{
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
}
public bool Equals(List<T> x, List<T> y)
{
return x.SetEquals(y, valueComparer);
}
public int GetHashCode(List<T> obj)
{
throw new NotImplementedException();
}
}
Используется следующий метод расширения:
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second,
IEqualityComparer<T> comparer)
{
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
}
Теперь мы можем просто написать:
new DictionaryComparer<string, List<int>>(new ListComparer<int>())
.Equals(dict1, dict2);
Ответ 2
Я знаю, что этот вопрос уже имеет принятый ответ, но я хотел бы предложить еще более простую альтернативу:
using System.Linq;
using System.Collections.Generic;
namespace Foo
{
public static class DictionaryExtensionMethods
{
public static bool ContentEquals<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Dictionary<TKey, TValue> otherDictionary)
{
return (otherDictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key)
.SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key));
}
}
}
Ответ 3
Я думаю, что AreDictionariesEqual()
просто нужен другой метод для сравнения List
Итак, если порядок записей не имеет значения, вы можете попробовать следующее:
static bool ListEquals(List<int> L1, List<int> L2)
{
if (L1.Count != L2.Count)
return false;
return L1.Except(L2).Count() == 0;
}
/*
if it is ok to change List content you may try
L1.Sort();
L2.Sort();
return L1.SequenceEqual(L2);
*/
static bool DictEquals(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
{
if (D1.Count != D2.Count)
return false;
return D1.Keys.All(k => D2.ContainsKey(k) && ListEquals(D1[k],D2[k]));
}
И если порядок записей имеет значение, попробуйте следующее:
static bool DictEqualsOrderM(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
{
if (D1.Count != D2.Count)
return false;
//check keys for equality, than lists.
return (D1.Keys.SequenceEqual(D2.Keys) && D1.Keys.All(k => D1[k].SequenceEqual(D2[k])));
}
Ответ 4
Принятый ответ не всегда возвращает правильное сравнение, потому что
использование HashSet для сравнения 2 списков не учитывает повторяющиеся значения в списках.
Например, если OP:
var dict1 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 1, 2, 1 } } };
var dict2 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 2, 2, 1 } } };
Тогда результат сравнения словаря - они равны, когда они не являются. Единственное решение, которое я вижу, - сортировать список 2 и сравнивать значения по индексу, но я уверен, что кто-то умнее, чем я, может придумать более эффективный способ.
Ответ 5
Преобразуйте словарь в список KeyValuePair, а затем сравните его как коллекции:
CollectionAssert.AreEqual(
dict1.OrderBy(kv => kv.Key).ToList(),
dict2.OrderBy(kv => kv.Key).ToList()
)
Ответ 6
Если известно, что два словаря используют эквивалентные реализации IEqualityComparer
, и каждый хочет считать эквивалентными все ключи, которые эта реализация рассматривает как эквивалентные, они содержат одинаковое количество элементов, а одно (произвольно выбранное) отображает все ключи элементов, найденные в другом, к соответствующим значениям от другого, они будут эквивалентны до тех пор, пока один из них не будет изменен. Тестирование этих условий будет быстрее любого подхода, который не предполагает, что оба словаря используют один и тот же IEqualityComparer
.
Если два словаря не используют одну и ту же реализацию IEqualityComparer
, они обычно не считаются эквивалентными независимо от элементов, которые они содержат. Например, Dictionary<String,String>
с чувствительным к регистру компаратором и один с нечувствительным к регистру компаратором, оба из которых содержат пару "ключ-значение" ( "Fred" , "Quimby" ), не эквивалентны, поскольку последний будет отображать "FRED" до "Quimby" , но первый не будет.
Только если словари используют ту же реализацию IEqualityComparer
, но если вас интересует более тонкое определение равенства ключей, чем тот, который используется словарями, и копия ключа не сохраняется с каждым значением, необходимо будет создать новый словарь для тестирования оригинальных словарей для равенства. Возможно, лучше всего отложить этот шаг до тех пор, пока предыдущий тест не предложит совпадение словарей. Затем постройте Dictionary<TKey,TKey>
, который отображает каждую клавишу из одного из словарей в себя, а затем просматривает все другие ключи словаря, чтобы убедиться, что они сопоставляются с вещами, которые соответствуют. Если в обоих словарях использовались нечувствительные к регистру сравнения, и один из них ( "Fred" , "Quimby" ) и другой ( "FRED", "Quimby" ), новый временный словарь отображал бы "FRED" на "Fred" и сравнивал эти две строки показывают, что словари не совпадают.
Ответ 7
Вот как использовать Linq, возможно, жертвуя некоторой эффективностью для аккуратного кода. В другом примере Linq от jfren484 фактически не выполняется тест DoesOrderValuesMatter(), поскольку он зависит от значения по умолчанию Equals() для List<int>
, который зависит от порядка.
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
string dict1string = String.Join(",", dict1.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
string dict2string = String.Join(",", dict2.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
return dict1string.Equals(dict2string);
}
Ответ 8
Мне нравится этот подход, потому что он дает более подробную информацию, когда тест терпит неудачу
public void AssertSameDictionary<TKey,TValue>(Dictionary<TKey,TValue> expected,Dictionary<TKey,TValue> actual)
{
string d1 = "expected";
string d2 = "actual";
Dictionary<TKey,TValue>.KeyCollection keys1= expected.Keys;
Dictionary<TKey,TValue>.KeyCollection keys2= actual.Keys;
if (actual.Keys.Count > expected.Keys.Count)
{
string tmp = d1;
d1 = d2;
d2 = tmp;
Dictionary<TKey, TValue>.KeyCollection tmpkeys = keys1;
keys1 = keys2;
keys2 = tmpkeys;
}
foreach(TKey key in keys1)
{
Assert.IsTrue(keys2.Contains(key), $"key '{key}' of {d1} dict was not found in {d2}");
}
foreach (TKey key in expected.Keys)
{
//already ensured they both have the same keys
Assert.AreEqual(expected[key], actual[key], $"for key '{key}'");
}
}
Ответ 9
public static IDictionary<string, object> ToDictionary(this object source)
{
var fields = source.GetType().GetFields(
BindingFlags.GetField |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source) ?? string.Empty
);
var properties = source.GetType().GetProperties(
BindingFlags.GetField |
BindingFlags.GetProperty |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source, null) ?? string.Empty
);
return fields.Concat(properties).ToDictionary(key => key.Key, value => value.Value); ;
}
public static bool EqualsByValue(this object source, object destination)
{
var firstDic = source.ToFlattenDictionary();
var secondDic = destination.ToFlattenDictionary();
if (firstDic.Count != secondDic.Count)
return false;
if (firstDic.Keys.Except(secondDic.Keys).Any())
return false;
if (secondDic.Keys.Except(firstDic.Keys).Any())
return false;
return firstDic.All(pair =>
pair.Value.ToString().Equals(secondDic[pair.Key].ToString())
);
}
public static bool IsAnonymousType(this object instance)
{
if (instance == null)
return false;
return instance.GetType().Namespace == null;
}
public static IDictionary<string, object> ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary<string, object> parentPropertyValue = null)
{
var propsDic = parentPropertyValue ?? new Dictionary<string, object>();
foreach (var item in source.ToDictionary())
{
var key = string.IsNullOrEmpty(parentPropertyKey) ? item.Key : $"{parentPropertyKey}.{item.Key}";
if (item.Value.IsAnonymousType())
return item.Value.ToFlattenDictionary(key, propsDic);
else
propsDic.Add(key, item.Value);
}
return propsDic;
}
Ответ 10
Большинство ответов повторяют словари несколько раз, хотя это должно быть просто:
static bool AreEqual(IDictionary<string, string> thisItems, IDictionary<string, string> otherItems)
{
if (thisItems.Count != otherItems.Count)
{
return false;
}
var thisKeys = thisItems.Keys;
foreach (var key in thisKeys)
{
if (!(otherItems.TryGetValue(key, out var value) &&
string.Equals(thisItems[key], value, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
return true;
}