2009-03-24 26 views
44

Me gustaría recibir algunos comentarios sobre cómo podemos escribir mejor una función genérica que permita comparar dos listas. Las listas contienen objetos de clase y nos gustaría iterar a través de una lista, buscando el mismo elemento en una segunda lista e informar cualquier diferencia.Comparar dos listas para las diferencias

Ya tenemos un método para comparar clases, por lo que necesitamos comentarios sobre cómo podemos alimentar el método (que se muestra a continuación) de dos listas.

Por ejemplo, supongamos que tenemos una clase simple de "Empleado" que tiene tres propiedades, Nombre, ID, Departamento. Queremos informar las diferencias entre List y otra lista.

Nota:
Ambas listas contendrán siempre la misma cantidad de elementos.

Como se mencionó anteriormente, tenemos un método genérico que utilizamos para comparar dos clases, ¿cómo podemos incorporar este método para atender listas, es decir, desde otro método, recorrer la Lista y alimentar las clases al método genérico. ... pero ¿cómo encontramos la clase equivalente en la segunda lista para pasar al siguiente método;

public static string CompareTwoClass_ReturnDifferences<T1, T2>(T1 Orig, T2 Dest) 
    where T1 : class 
    where T2 : class 
{ 
    // Instantiate if necessary 
    if (Dest == null) throw new ArgumentNullException("Dest", "Destination class must first be instantiated."); 

    var Differences = CoreFormat.StringNoCharacters; 

    // Loop through each property in the destination 
    foreach (var DestProp in Dest.GetType().GetProperties()) 
    { 
     // Find the matching property in the Orig class and compare 
     foreach (var OrigProp in Orig.GetType().GetProperties()) 
     { 

      if (OrigProp.Name != DestProp.Name || OrigProp.PropertyType != DestProp.PropertyType) continue; 
      if (OrigProp.GetValue(Orig, null).ToString() != DestProp.GetValue(Dest, null).ToString()) 
       Differences = Differences == CoreFormat.StringNoCharacters 
        ? string.Format("{0}: {1} -> {2}", OrigProp.Name, 
                 OrigProp.GetValue(Orig, null), 
                 DestProp.GetValue(Dest, null)) 
        : string.Format("{0} {1}{2}: {3} -> {4}", Differences, 
                   Environment.NewLine, 
                   OrigProp.Name, 
                   OrigProp.GetValue(Orig, null), 
                   DestProp.GetValue(Dest, null)); 
     } 
    } 
    return Differences; 
} 

¿Alguna sugerencia o idea apreciada?

Editar: Targeting .NET 2.0 por lo que LINQ está fuera de la cuestión.

+0

lol ... no, un nivel superior, sistema de aplicación crítica :-) serio, tratando de implementar esta funcionalidad en una pequeña aplicación manía ... todo el aprendizaje. –

+0

¿Las listas son de igual longitud? – Noldorin

+0

sí, las listas tienen la misma longitud –

Respuesta

15

.... pero ¿cómo encontramos la clase equivalente en la segunda lista para pasar al método a continuación;

Este es su problema real; debe tener al menos una propiedad inmutable, una identificación o algo así, para identificar los objetos correspondientes en ambas listas. Si no tiene esa propiedad, no puede resolver el problema sin errores. Puede tratar de adivinar los objetos correspondientes buscando cambios mínimos o lógicos.

Si tiene tal propiedad, la solución se vuelve realmente simple.

Enumerable.Join(
    listA, listB, 
    a => a.Id, b => b.Id, 
    (a, b) => CompareTwoClass_ReturnDifferences(a, b)) 

Gracias a los dos danbruc y noldorin por su colaboración. ambas Listas tendrán la misma longitud y en el mismo orden. por lo que el método anterior está cerca, pero ¿puedes modificar este método para pasar la enumeración.Actual al método que publiqué anteriormente?

Ahora estoy confundido ... ¿cuál es el problema con eso? ¿Por qué no solo lo siguiente?

for (Int32 i = 0; i < Math.Min(listA.Count, listB.Count); i++) 
{ 
    yield return CompareTwoClass_ReturnDifferences(listA[i], listB[i]); 
} 

La llamada Math.Min() puede incluso omitirse si se garantiza la misma longitud.


aplicación de noldorin es, por supuesto, más inteligente debido a la delegada y el uso de los encuestadores en lugar de utilizar ICollection.

+0

ok, supongamos que Employee.ID nunca cambiará. –

+0

lo sentimos, estamos apuntando a .NET 2.0. Debería haber hecho este punto. –

+0

LINQ * puede * ejecutarse en .NET 2.0. Consulte http://code.google.com/p/linqbridge/ –

6

Creo que estás en busca de un método como este:

public static IEnumerable<TResult> CompareSequences<T1, T2, TResult>(IEnumerable<T1> seq1, 
    IEnumerable<T2> seq2, Func<T1, T2, TResult> comparer) 
{ 
    var enum1 = seq1.GetEnumerator(); 
    var enum2 = seq2.GetEnumerator(); 

    while (enum1.MoveNext() && enum2.MoveNext()) 
    { 
     yield return comparer(enum1.Current, enum2.Current); 
    } 
} 

Es no probado, pero debe hacer el trabajo, no obstante. Tenga en cuenta que lo que es particularmente útil acerca de este método es que es completamente genérico, es decir, puede tomar dos secuencias de tipos arbitrarios (y diferentes) y devolver objetos de cualquier tipo.

Por supuesto, esta solución supone que desea comparar el enésimo artículo de seq1 con el enésimo elemento en seq2. Si desea hacer coincidir los elementos en las dos secuencias en función de una propiedad/comparación particular, entonces querrá realizar algún tipo de operación join (como lo sugiere danbruc usando Enumerable.Join. Hágame saber si ninguno de estos enfoques es bastante lo que busco y tal vez puedo sugerir algo más

Editar:.. Aquí está un ejemplo de cómo se puede utilizar el método CompareSequences con la función comparador informados originalmente

// Prints out to the console all the results returned by the comparer function (CompareTwoClass_ReturnDifferences in this case). 
var results = CompareSequences(list1, list2, CompareTwoClass_ReturnDifferences); 
int index;  

foreach(var element in results) 
{ 
    Console.WriteLine("{0:#000} {1}", index++, element.ToString()); 
} 
+0

Esto recorrerá ambas listas de forma sincrónica, pero es posible que el objeto no se solicite del mismo modo en ambas listas. –

+0

Sí, por supuesto.No pude entender la pregunta si este era el caso o no, pero de todos modos solo edité la publicación antes de leer su comentario, así que ahora es propiedad calificada ... – Noldorin

+0

gracias a ustedes tanto a Danbruc como a Noldorin por su realimentación. ambas listas tendrán la misma longitud y en el mismo orden. por lo que el método anterior está cerca, pero ¿puedes modificar este método para pasar la enumeración actual del método que publiqué anteriormente? –

1

Espero que estoy haciendo su pregunta correctamente, pero puede hacer esto Rápidamente con Linq. Supongo que, universalmente, siempre tendrá una propiedad Id. Solo crea una interfaz para asegurar esto.

Si la forma en que identifica un objeto para que sea el mismo cambio de clase a clase, recomendaría pasar un delegado que devuelva verdadero si los dos objetos tienen la misma identificación persistente.

Aquí es cómo hacerlo en LINQ:

List<Employee> listA = new List<Employee>(); 
     List<Employee> listB = new List<Employee>(); 

     listA.Add(new Employee() { Id = 1, Name = "Bill" }); 
     listA.Add(new Employee() { Id = 2, Name = "Ted" }); 

     listB.Add(new Employee() { Id = 1, Name = "Bill Sr." }); 
     listB.Add(new Employee() { Id = 3, Name = "Jim" }); 

     var identicalQuery = from employeeA in listA 
          join employeeB in listB on employeeA.Id equals employeeB.Id 
          select new { EmployeeA = employeeA, EmployeeB = employeeB }; 

     foreach (var queryResult in identicalQuery) 
     { 
      Console.WriteLine(queryResult.EmployeeA.Name); 
      Console.WriteLine(queryResult.EmployeeB.Name); 
     } 
+0

-1 comparando id no es lo suficientemente minucioso. – aggietech

72

Esta solución produce una lista de resultados, que contiene todas las diferencias de ambas listas de entrada. Puede comparar sus objetos por cualquier propiedad, en mi ejemplo es ID. La única restricción es que las listas deben ser del mismo tipo:

var DifferencesList = ListA.Where(x => !ListB.Any(x1 => x1.id == x.id)) 
      .Union(ListB.Where(x => !ListA.Any(x1 => x1.id == x.id))); 
+6

Esto es genial. Las comunidades realmente te permiten obtener la opinión de otra persona sobre un problema. ¡Gracias! – Jeremy

+1

¡Funciona perfecto! Gracias – Haris

+0

Creo que esto supone que no hay duplicados en ninguna de las listas. – NStuke

2

Este enfoque de Microsoft funciona muy bien y ofrece la opción de comparar una lista a otra y cambiar ellos para obtener la diferencia de cada uno. Si está comparando clases, simplemente agregue sus objetos a dos listas separadas y luego ejecute la comparación.

http://msdn.microsoft.com/en-us/library/bb397894.aspx

Cuestiones relacionadas