¿Existe un método incorporado para comparar colecciones?

178

Me gustaría comparar el contenido de un par de colecciones en mi método Equals. Tengo un diccionario y una lista IL. ¿Hay un método incorporado para hacer esto?

Editado: Quiero comparar dos Diccionarios y dos IListas, por lo que creo que lo que significa igualdad está claro: si los dos diccionarios contienen las mismas claves asignadas a los mismos valores, entonces son iguales.

TimK
fuente
Sin importar el orden o no IList? La pregunta es ambigua.
nawfal
Enumerable.SequenceEqualy ISet.SetEqualsproporcionar versiones de esta funcionalidad. Si desea ser independiente de los pedidos y trabajar con colecciones que tienen duplicados, deberá rodar los suyos. Echa un vistazo a la implementación sugerida en esta publicación
ChaseMedallion el

Respuestas:

185

Enumerable.SequenceEqual

Determina si dos secuencias son iguales comparando sus elementos utilizando un IEqualityComparer (T) especificado.

No puede comparar directamente la lista y el diccionario, pero puede comparar la lista de valores del Diccionario con la lista

Glenn Slaven
fuente
52
El problema es que SequenceEqual espera que los elementos estén en el mismo orden. La clase Dictionary no garantiza el orden de las claves o los valores al enumerar, por lo que si va a usar SequenceEqual, ¡primero debe ordenar los .Keys y .Values!
Orion Edwards el
3
@Orion: ... a menos que desee detectar diferencias de orden, por supuesto :-)
schoetbi
30
@schoetbi: ¿Por qué querrías detectar diferencias de pedido en un contenedor que no garantiza el pedido ?
Matti Virkkunen
44
@schoetbi: es para sacar cierto elemento de un IEnumerable. Sin embargo, un diccionario no garantizar el orden, por lo que .Keysy .Valuespuede devolver las claves y valores en cualquier orden que se sienten como, y que el orden es probable que el cambio como el diccionario se modifica también. Le sugiero que lea sobre qué es un diccionario y qué no es.
Matti Virkkunen
55
TestTools y NUnit de MS proporcionan CollectionAssert.AreEquivalent
tymtam el
44

Como otros han sugerido y han notado, SequenceEquales sensible al orden. Para resolver eso, puede ordenar el diccionario por clave (que es único y, por lo tanto, el orden siempre es estable) y luego usarlo SequenceEqual. La siguiente expresión verifica si dos diccionarios son iguales independientemente de su orden interno:

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

EDITAR: Como señaló Jeppe Stig Nielsen, algunos objetos tienen un IComparer<T>incompatible con ellos IEqualityComparer<T>, lo que arroja resultados incorrectos. Cuando utilice claves con dicho objeto, debe especificar una correcta IComparer<T>para esas claves. Por ejemplo, con las claves de cadena (que exhiben este problema), debe hacer lo siguiente para obtener resultados correctos:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
Allon Guralnek
fuente
¿Qué pasa si el tipo de clave no lo hará CompareTo? Su solución explotará entonces. ¿Y si el tipo de clave tiene un comparador predeterminado que es incompatible con su comparador de igualdad predeterminado? Este es el caso string, ya sabes. Como ejemplo, estos diccionarios (con comparadores de igualdad implícitos predeterminados) no pasarán la prueba (según todas las informaciones culturales que conozco):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: En cuanto a la incompatibilidad entre IComparery IEqualityComparer- No estaba al tanto de este problema, ¡muy interesante! Actualicé la respuesta con una posible solución. Sobre la falta de CompareTo, creo que el desarrollador debe asegurarse de que el delegado proporcionado al OrderBy()método devuelva algo comparable. Creo que esto es cierto para cualquier uso o OrderBy(), incluso fuera de las comparaciones de diccionario.
Allon Guralnek
15

Además del mencionado SequenceEqual , que

es cierto si dos listas tienen la misma longitud y sus elementos correspondientes se comparan igual según un comparador

(que puede ser el comparador predeterminado, es decir, una anulación Equals())

Vale la pena mencionar que en .Net4 hay SetEquals en los ISetobjetos, que

ignora el orden de los elementos y cualquier elemento duplicado.

Entonces, si desea tener una lista de objetos, pero no es necesario que estén en un orden específico, considere que un ISet(como a HashSet) puede ser la opción correcta.

Destino
fuente
7

Eche un vistazo al método 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);
aku
fuente
13
Esto no es confiable porque SequenceEqual espera que los valores salgan del diccionario en un orden confiable: el diccionario no ofrece tales garantías sobre el orden y el diccionario. Las teclas podrían aparecer como [2, 1] en lugar de [1, 2] y su prueba fallaría
Orion Edwards
5

.NET carece de herramientas poderosas para comparar colecciones. He desarrollado una solución simple que puede encontrar en el siguiente enlace:

http://robertbouillon.com/2010/04/29/comparing-collections-in-net/

Esto realizará una comparación de igualdad independientemente del orden:

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

Esto verificará si se agregaron / eliminaron elementos:

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

Esto verá qué elementos del diccionario cambiaron:

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
usuario329244
fuente
10
Por supuesto, .NET tiene herramientas poderosas para comparar colecciones (son operaciones basadas en conjuntos). .Removedes lo mismo que list1.Except(list2), .Addedes list2.Except(list1), .Equales list1.Intersect(list2)y .Differentes original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Puedes hacer casi cualquier comparación con LINQ.
Allon Guralnek
3
Corrección: .Differentes 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). E incluso puede agregar OldValue = left.Valuesi necesita el valor anterior también.
Allon Guralnek
3
@AllonGuralnek sus sugerencias son buenas, pero no manejan el caso donde la Lista no es un conjunto verdadero, donde la lista contiene el mismo objeto varias veces. La comparación de {1, 2} y {1, 2, 2} no devolvería nada agregado / eliminado.
Niall Connaughton
4

No sabía sobre el método Enumerable.SequenceEqual (aprendes algo todos los días ...), pero iba a sugerir usar un método de extensión; algo como esto:

    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;

    }

Curiosamente, después de tomar 2 segundos para leer sobre SequenceEqual, parece que Microsoft ha desarrollado la función que le describí.

Giovanni Galbo
fuente
4

Esto no responde directamente a sus preguntas, pero tanto TestTools de MS como NUnit proporcionan

 CollectionAssert.AreEquivalent

que hace más o menos lo que quieres.

tymtam
fuente
Estaba buscando esto para mi prueba de NUnit
Blem
1

Para comparar colecciones, también puede usar LINQ. Enumerable.Intersectdevuelve todos los pares que son iguales. Puede comparar dos diccionarios como este:

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

La primera comparación es necesaria porque dict2puede contener todas las claves dict1y más.

También puede usar pensar en variaciones usando Enumerable.Excepty Enumerable.Unionque conducen a resultados similares. Pero se puede usar para determinar las diferencias exactas entre conjuntos.

Crono
fuente
1

¿Qué tal este ejemplo?

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

Cortesía: https://www.dotnetperls.com/dictionary-equals

ispostback
fuente
value! = pair. El valor está haciendo una comparación de referencia, use Equals en su lugar
kofifus
1

Para colecciones ordenadas (List, Array) use SequenceEqual

para uso de HashSet SetEquals

para Diccionario puedes hacer:

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

(Una solución más optimizada utilizará la clasificación, pero eso requerirá IComparable<TValue>)

kofifus
fuente
0

No. El marco de la colección no tiene ningún concepto de igualdad. Si lo piensa, no hay forma de comparar colecciones que no sean subjetivas. Por ejemplo, si compara su IList con su Diccionario, ¿serían iguales si todas las claves estuvieran en la IList, todos los valores estuvieran en la IList o si ambas estuvieran en la IList? No hay una forma obvia de comparar estas dos colecciones sin saber para qué se utilizarán, por lo que no tiene sentido un método igual de propósito general.

Andy malvado
fuente
0

No, porque el marco no sabe cómo comparar el contenido de sus listas.

Echa un vistazo a esto:

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

Mark Ingram
fuente
3
¿No hay ya varias opciones para decirle al marco cómo comparar los elementos? IComparer<T>, Anulando object.Equals, IEquatable<T>, IComparable<T>...
Stefan 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
fuente
0

No hubo, no es y podría no ser, al menos eso creo. La razón detrás de esto es la igualdad de colección es probablemente un comportamiento definido por el usuario.

Se supone que los elementos en las colecciones no están en un orden particular, aunque sí tienen un orden natural, no es en lo que deben confiar los algoritmos de comparación. Digamos que tiene dos colecciones de:

{1, 2, 3, 4}
{4, 3, 2, 1}

¿Son iguales o no? Debes saber pero no sé cuál es tu punto de vista.

Las colecciones están conceptualmente desordenadas de forma predeterminada, hasta que los algoritmos proporcionan las reglas de clasificación. Lo mismo que SQL Server le llamará la atención es que cuando intenta hacer paginación, requiere que proporcione reglas de clasificación:

https://docs.microsoft.com/en-US/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017

Otras dos colecciones más:

{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}

De nuevo, ¿son iguales o no? Dígame usted ..

La repetibilidad de elementos de una colección juega su papel en diferentes escenarios y algunas colecciones como Dictionary<TKey, TValue>ni siquiera permiten elementos repetidos.

Creo que este tipo de igualdad está definida por la aplicación y, por lo tanto, el marco no proporcionó todas las implementaciones posibles.

Bueno, en casos generales Enumerable.SequenceEquales lo suficientemente bueno pero devuelve falso en el siguiente caso:

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

Leí algunas respuestas a preguntas como esta (puede buscarlas en Google ) y lo que usaría, en general:

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

Eso significa que una colección representa a la otra en sus elementos, incluidos los tiempos repetidos sin tener en cuenta el pedido original. Algunas notas de la implementación:

  • GetHashCode()es solo para ordenar, no para la igualdad; Creo que es suficiente en este caso

  • Count() realmente no enumera la colección y cae directamente en la implementación de propiedad de ICollection<T>.Count

  • Si las referencias son iguales, es solo Boris

Ken Kin
fuente