Me gustaría comparar dos colecciones (en C #), pero no estoy seguro de la mejor manera de implementar esto de manera eficiente.
He leído el otro hilo sobre Enumerable.SequenceEqual , pero no es exactamente lo que estoy buscando.
En mi caso, dos colecciones serían iguales si ambas contienen los mismos artículos (sin importar el orden).
Ejemplo:
collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};
collection1 == collection2; // true
Lo que generalmente hago es recorrer cada elemento de una colección y ver si existe en la otra colección, luego recorrer cada elemento de la otra colección y ver si existe en la primera colección. (Comienzo comparando las longitudes).
if (collection1.Count != collection2.Count)
return false; // the collections are not equal
foreach (Item item in collection1)
{
if (!collection2.Contains(item))
return false; // the collections are not equal
}
foreach (Item item in collection2)
{
if (!collection1.Contains(item))
return false; // the collections are not equal
}
return true; // the collections are equal
Sin embargo, esto no es del todo correcto, y probablemente no sea la forma más eficiente de comparar dos colecciones para la igualdad.
Un ejemplo que puedo pensar que estaría mal es:
collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}
Lo que sería igual a mi implementación. ¿Debo contar la cantidad de veces que se encuentra cada artículo y asegurarme de que los recuentos sean iguales en ambas colecciones?
Los ejemplos están en algún tipo de C # (llamémoslo pseudo-C #), pero dé su respuesta en el idioma que desee, no importa.
Nota: Usé números enteros en los ejemplos por simplicidad, pero también quiero poder usar objetos de tipo referencia (no se comportan correctamente como claves porque solo se compara la referencia del objeto, no el contenido).
fuente
Respuestas:
Resulta que Microsoft ya tiene esto cubierto en su marco de prueba: CollectionAssert.AreEquivalent
Usando el reflector, modifiqué el código detrás de AreEquivalent () para crear un comparador de igualdad correspondiente. Es más completo que las respuestas existentes, ya que tiene en cuenta los valores nulos, implementa IEqualityComparer y tiene algunas comprobaciones de eficiencia y casos extremos. Además, es Microsoft :)
Uso de la muestra:
O si solo desea comparar dos colecciones directamente:
Finalmente, puede usar su comparador de igualdad de su elección:
fuente
EqualityComparer
(ya sea el que proporcionó oEqualityComparer.Default
puede verificar Reflector o la fuente de referencia para verificar esto). Es cierto que si los objetos cambian (y específicamente sus cambios de código hash) mientras se ejecuta este método, los resultados son inesperados, pero eso solo significa que este método no es seguro para subprocesos en este contexto.EqualityComparer
(oEqualityComparer.Default
si no se especificó ninguno) y nuevamente la implementación es correcta.Equals
debido a laIEqualityComparer<T>
interfaz. Lo que debería mirar es el nombre del comparador en sí . En este caso es loMultiSetComparer
que tiene sentido.Una solución simple y bastante eficiente es clasificar ambas colecciones y luego compararlas para lograr la igualdad:
Este algoritmo es O (N * logN), mientras que su solución anterior es O (N ^ 2).
Si las colecciones tienen ciertas propiedades, puede implementar una solución más rápida. Por ejemplo, si ambas colecciones son conjuntos hash, no pueden contener duplicados. Además, verificar si un conjunto hash contiene algún elemento es muy rápido. En ese caso, un algoritmo similar al suyo probablemente sería el más rápido.
fuente
Cree un diccionario "dict" y luego, para cada miembro de la primera colección, haga dict [member] ++;
Luego, repita la segunda colección de la misma manera, pero para cada miembro haga dict [miembro] -.
Al final, repita todos los miembros del diccionario:
Editar: Por lo que puedo decir, esto está en el mismo orden que el algoritmo más eficiente. Este algoritmo es O (N), suponiendo que el Diccionario utilice búsquedas O (1).
fuente
return dict.All(kvp => kvp.Value == 0);
Esta es mi implementación genérica (muy influenciada por D.Jennings) del método de comparación (en C #):
fuente
The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"
- esto no es verdad. El algoritmo se basa en suposiciones incorrectas y, aunque funciona, es terriblemente ineficiente.Podrías usar un Hashset . Mira el método SetEquals .
fuente
Si usa Shouldly , puede usar ShouldAllBe with Contains.
Y finalmente, puedes escribir una extensión.
ACTUALIZAR
Existe un parámetro opcional en el método shouldbe .
fuente
bool ignoreOrder
en el método shouldbe .EDITAR: me di cuenta tan pronto como planteé que esto realmente solo funciona para conjuntos, no tratará adecuadamente con colecciones que tienen elementos duplicados. Por ejemplo, {1, 1, 2} y {2, 2, 1} se considerarán iguales desde la perspectiva de este algoritmo. Sin embargo, si sus colecciones son conjuntos (o su igualdad se puede medir de esa manera), espero que encuentre útil lo siguiente.
La solución que uso es:
Linq hace lo del diccionario debajo de las cubiertas, por lo que también es O (N). (Tenga en cuenta que es O (1) si las colecciones no son del mismo tamaño).
Hice una comprobación de cordura utilizando el método "SetEqual" sugerido por Daniel, el método OrderBy / SequenceEquals sugerido por Igor y mi sugerencia. Los resultados están a continuación, mostrando O (N * LogN) para Igor y O (N) para el mío y el de Daniel.
Creo que la simplicidad del código de intersección de Linq lo convierte en la solución preferible.
fuente
En el caso de que no se repitan ni se ordene, se puede usar el siguiente EqualityComparer para permitir colecciones como claves de diccionario:
Aquí está la implementación ToHashSet () que utilicé. El algoritmo de código hash proviene de Effective Java (a través de Jon Skeet).
fuente
ISet<T>
para expresar que está destinada a conjuntos (es decir, sin duplicados).ISet
, la idea aquí era tratar elIEnumerable
como un conjunto (porque tienes unIEnumerable
para empezar), aunque teniendo en cuenta los 0 votos a favor 5 años que tal vez no hayan sido la mejor idea: PLa solución requiere .NET 3.5 y el
System.Collections.Generic
espacio de nombres. Según Microsoft ,SymmetricExceptWith
es una operación O (n + m) , donde n representa el número de elementos en el primer conjunto ym representa el número de elementos en el segundo. Siempre puede agregar un comparador de igualdad a esta función si es necesario.fuente
¿Por qué no usar .Except ()
http://msdn.microsoft.com/en-us/library/bb397894.aspx
fuente
Except
no funcionará para contar elementos duplicados. Volverá verdadero para los conjuntos {1,2,2} y {1,1,2}.[1, 1, 2] != [1, 2, 2]
. Usar losDistinct
haría parecer iguales.Una publicación duplicada, pero mira mi solución para comparar colecciones . Es bastante simple:
Esto realizará una comparación de igualdad independientemente del orden:
Esto verificará si se agregaron / eliminaron elementos:
Esto verá qué elementos del diccionario cambiaron:
Publicación original aquí .
fuente
erickson tiene casi razón: dado que desea hacer coincidir los recuentos de duplicados, quiere una bolsa . En Java, esto se parece a:
Estoy seguro de que C # tiene una implementación de Set incorporada. Yo usaría eso primero; Si el rendimiento es un problema, siempre puede usar una implementación de Set diferente, pero use la misma interfaz de Set.
fuente
Aquí está mi variante de método de extensión de la respuesta de ohadsc, en caso de que sea útil para alguien
fuente
IEnumerable<T>
s son consultas, entonces llamarCount()
no es una buena idea. El enfoque de la respuesta original de Ohad de verificar si lo sonICollection<T>
es la mejor idea.Aquí hay una solución que es una mejora con respecto a esta .
fuente
Hay muchas soluciones a este problema. Si no te importan los duplicados, no tienes que ordenar ambos. Primero asegúrese de que tengan la misma cantidad de artículos. Después de eso, una de las colecciones. Luego, busque cada elemento de la segunda colección en la colección ordenada. Si no encuentra un artículo determinado, deténgase y devuelva falso. La complejidad de esto: - ordenar la primera colección: N Log (N) - buscar cada elemento del segundo al primero: NLOG (N) para que termines con 2 * N * LOG (N) suponiendo que coincidan y busques todo. Esto es similar a la complejidad de ordenar ambos. Además, esto le brinda el beneficio de detenerse antes si hay una diferencia. Sin embargo, tenga en cuenta que si ambos se ordenan antes de pasar a esta comparación e intenta ordenarlos usando algo como qsort, la clasificación será más costosa. Hay optimizaciones para esto. Otra alternativa, que es ideal para pequeñas colecciones en las que conoce el rango de los elementos, es usar un índice de máscara de bits. Esto le dará un rendimiento O (n). Otra alternativa es usar un hash y buscarlo. Para colecciones pequeñas, generalmente es mucho mejor hacer la clasificación o el índice de máscara de bits. Hashtable tiene la desventaja de una peor localidad, así que tenlo en cuenta. De nuevo, eso solo si no lo haces No importa los duplicados. Si desea tener en cuenta los duplicados, vaya ordenando ambos.
fuente
En muchos casos, la única respuesta adecuada es la de Igor Ostrovsky, otras respuestas se basan en el código hash de los objetos. Pero cuando genera un código hash para un objeto, lo hace solo en función de sus campos INMUTABLES, como el campo Id de objeto (en el caso de una entidad de base de datos). ¿Por qué es importante anular GetHashCode cuando se anula el método Equals?
Esto significa que si compara dos colecciones, el resultado podría ser cierto para el método de comparación, aunque los campos de los diferentes elementos no sean iguales. Para comparar colecciones en profundidad, debe utilizar el método de Igor e implementar IEqualirity.
Lea los comentarios míos y del Sr.Schnider en su publicación más votada.
James
fuente
Permitiendo duplicados en
IEnumerable<T>
(si los conjuntos no son deseables \ posibles) e "ignorando el orden", debería poder usar a.GroupBy()
.No soy un experto en las mediciones de complejidad, pero mi comprensión rudimentaria es que esto debería ser O (n). Entiendo que O (n ^ 2) proviene de realizar una operación O (n) dentro de otra operación O (n) como
ListA.Where(a => ListB.Contains(a)).ToList()
. Se evalúa la igualdad de cada elemento de la Lista B con respecto a cada elemento de la Lista A.Como dije, mi comprensión de la complejidad es limitada, así que corrígeme si me equivoco.
fuente
Esta solución simple obliga
IEnumerable
a implementar el tipo genéricoIComparable
. PorOrderBy
la definición de.Si no desea hacer tal suposición pero aún quiere usar esta solución, puede usar el siguiente código:
fuente
Si se compara con el propósito de Afirmaciones de Prueba de Unidad, puede tener sentido tirar algo de eficiencia por la ventana y simplemente convertir cada lista en una representación de cadena (csv) antes de hacer la comparación. De esa manera, el mensaje de Aserción de prueba predeterminado mostrará las diferencias dentro del mensaje de error.
Uso:
Método de extensión auxiliar:
fuente