referencias programacion posible numeros lenguaje intencionada comparar comparacion codigos basicos c# unit-testing dictionary

programacion - posible comparacion de referencias no intencionada c#



C#Compara dos diccionarios para la igualdad (9)

Quiero comparar en C # dos diccionarios con como claves una string y como valor una lista de int s. Supongo que dos diccionarios son iguales cuando ambos tienen las mismas claves y para cada clave como valor una lista con los mismos enteros (ambos no necesariamente en el mismo orden).

Utilizo las respuestas de this y de this pregunta relacionada, pero ambas fallan en mi conjunto de pruebas para las funciones de prueba DoesOrderKeysMatter y DoesOrderValuesMatter .

Mi suite de prueba:

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

¿Cuál es la forma correcta de comparar este tipo de diccionarios? ¿O hay un error en mi TestSuite (aunque esté escrito con torpeza)?

Actualizar

Estoy tratando de incorporar la respuesta de Servy en mi conjunto de pruebas, como a continuación, pero recibo algunos errores (subrayados con una línea roja ondulada en Visual Studio):

  • SetEquals en el método `Equals dice:" no contiene una definición para que SetEquals acepte un primer argumento de tipo Generic.List.
  • En AreDictionariesEqual it says DictionaryComparer> es un tipo pero se usa como variable ».

    espacio de nombres UnitTestProject1 {[TestClass] clase pública ProviceReportTests {[TestMethod] // ... igual que arriba

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

    }


Aquí hay una forma de usar Linq, probablemente sacrificando algo de eficiencia por el código ordenado. El otro ejemplo de Linq de realmente falla la prueba DoesOrderValuesMatter (), porque depende de los valores por defecto de Equals () para List<int> , que depende de la orden.

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


Así que primero necesitamos un comparador de igualdad para los diccionarios. Debe asegurarse de que tengan claves coincidentes y, si las tienen, comparar los valores de cada clave:

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

pero esto no es suficiente por sí solo. Necesitamos comparar los valores del diccionario usando otro comparador personalizado, no el comparador predeterminado, ya que el comparador de lista predeterminado no mirará los valores de la lista:

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

Que utiliza el siguiente método de extensión:

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

Ahora podemos simplemente escribir:

new DictionaryComparer<string, List<int>>(new ListComparer<int>()) .Equals(dict1, dict2);


Convierta el diccionario a la lista de KeyValuePair y luego compare como colecciones:

CollectionAssert.AreEqual( dict1.OrderBy(kv => kv.Key).ToList(), dict2.OrderBy(kv => kv.Key).ToList() )


Creo que AreDictionariesEqual() solo necesita otro método para comparar AreDictionariesEqual()

Así que si el orden de las entradas no importa, puedes probar esto:

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

Y si el orden de las entradas importa, prueba esto:

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


La respuesta aceptada arriba no siempre devolverá una comparación correcta porque usar un HashSet para comparar 2 listas no contabilizará los valores duplicados en las listas. Por ejemplo, si el OP tenía:

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

Entonces el resultado de la comparación del diccionario es que son iguales, cuando no lo son. La única solución que veo es ordenar la lista 2 y comparar los valores por índice, pero estoy seguro de que alguien más inteligente que yo pueda encontrar una manera más eficiente.


Me gusta este enfoque porque da más detalles cuando falla la prueba.

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


Sé que esta pregunta ya tiene una respuesta aceptada, pero me gustaría ofrecer una alternativa aún más simple:

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


Si se sabe que dos diccionarios usan implementaciones equivalentes de IEqualityComparer , y uno desea considerar como equivalentes todas las claves que esa implementación considera como equivalentes, contienen la misma cantidad de elementos, y uno (elegido de forma arbitraria) mapea todos los elementos que se encuentran en las claves. el otro a los valores correspondientes del otro, serán equivalentes a menos o hasta que se modifique uno de ellos. Las pruebas para esas condiciones serán más rápidas que cualquier enfoque que no asuma que ambos diccionarios usan el mismo IEqualityComparer .

Si dos diccionarios no usan la misma implementación de IEqualityComparer , generalmente no deben considerarse equivalentes, independientemente de los elementos que contienen. Por ejemplo, un Dictionary<String,String> con un comparador que distingue entre mayúsculas y minúsculas y uno con un comparador que no distingue entre mayúsculas y minúsculas, ambos contienen el par clave-valor ("Fred", "Quimby") no son equivalentes, ya que este último asignaría "FRED" a "Quimby", pero el primero no lo haría.

Solo si los diccionarios utilizan la misma implementación de IEqualityComparer , pero si uno está interesado en una definición más precisa de la igualdad de claves que la utilizada por los diccionarios y no se almacena una copia de la clave con cada valor, será necesario para construir un nuevo diccionario con el fin de probar la igualdad de los diccionarios originales. Puede ser mejor retrasar este paso hasta que la prueba anterior sugiera que los diccionarios parecen coincidir. Luego Dictionary<TKey,TKey> un Dictionary<TKey,TKey> que Dictionary<TKey,TKey> cada clave de uno de los diccionarios a sí mismo, y luego busque todas las demás claves del diccionario para asegurarse de que se correspondan con las cosas que coinciden. Si ambos diccionarios utilizan comparadores que no distinguen entre mayúsculas y minúsculas, y uno contiene ("Fred", "Quimby") y el otro ("FRED", "Quimby"), el nuevo diccionario temporal asignará "FRED" a "Fred", y comparará esas dos cadenas revelarían que los diccionarios no coinciden.


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