2009-03-24 8 views
44

Vorrei un feedback su come possiamo scrivere una funzione generica che consenta di confrontare due elenchi. Gli elenchi contengono oggetti di classe e vorremmo iterare attraverso un elenco, cercando lo stesso elemento in un secondo elenco e riportare eventuali differenze.Confronta due elenchi per le differenze

Abbiamo già un metodo per confrontare le classi, quindi abbiamo bisogno di un feedback su come possiamo alimentare il metodo (mostrato sotto) da due elenchi.

Ad esempio, diciamo che abbiamo una semplice classe "Dipendente" che ha tre proprietà, Nome, ID, Dipartimento. Vogliamo segnalare le differenze tra Elenco e un altro Elenco.

Nota:
Entrambi gli elenchi contengono sempre lo stesso numero di elementi.

Come detto sopra, abbiamo un metodo generico che usiamo per confrontare due classi, come possiamo incorporare questo metodo per soddisfare gli elenchi, cioè da un altro metodo, scorrere l'elenco e alimentare le classi con il metodo generico. ... ma come troviamo la classe equivalente nel secondo Elenco per passare al metodo seguente;

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

Qualche suggerimento o idea apprezzata?

Modifica: Targeting .NET 2.0 in modo che LINQ sia fuori questione.

+0

lol ... no, un livello superiore, sistema di applicazioni critiche :-) sul serio, cercando di implementare questa funzionalità in un piccolo passatempo app ... è tutto di apprendimento. –

+0

Gli elenchi sono di uguale lunghezza? – Noldorin

+0

sì, le liste sono di uguale lunghezza –

risposta

15

.... ma come trovare la classe equivalente nel secondo Elenco da passare al metodo seguente;

Questo è il tuo problema reale; devi avere almeno una proprietà immutabile, un id o qualcosa del genere, per identificare gli oggetti corrispondenti in entrambi gli elenchi. Se non possiedi una proprietà del genere, non puoi risolvere il problema senza errori. Puoi semplicemente provare a indovinare gli oggetti corrispondenti cercando le modifiche minime o logiche.

Se si dispone di una tale proprietà, la soluzione diventa davvero semplice.

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

grazie a tutti e due e danbruc Noldorin per il suggerimento. entrambe le liste saranno la stessa lunghezza e nello stesso ordine. quindi il metodo sopra è vicino, ma puoi modificare questo metodo per passare l'enum.Current al metodo che ho postato sopra?

Ora sono confuso ... qual è il problema? Perché non solo il seguente?

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

La chiamata Math.Min() può anche essere omessa se viene garantita una lunghezza uguale.


implementazione di Noldorin è, naturalmente, più intelligente a causa del delegato e l'uso di enumeratori invece di utilizzare ICollection.

+0

ok, supponiamo che Employee.ID non cambierà mai. –

+0

scusate, stiamo prendendo di mira .NET 2.0. Avrei dovuto chiarire questo punto. –

+0

LINQ * può * essere eseguito su .NET 2.0. Vedi http://code.google.com/p/linqbridge/ –

6

Penso che tu sia alla ricerca di un metodo come questo:

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

E 'testato, ma dovrebbe fare il lavoro comunque. Si noti che ciò che è particolarmente utile in questo metodo è che è completamente generico, cioè può prendere due sequenze di tipi arbitrari (e diversi) e restituire oggetti di qualsiasi tipo.

Questa soluzione presuppone ovviamente che si desideri confrontare l'ennesimo articolo di seq1 con l'ennesimo elemento in seq2. Se vuoi fare corrispondere gli elementi nelle due sequenze in base a una particolare proprietà/confronto, allora ti consigliamo di eseguire una sorta di operazione join (come suggerito da danbruc usando Enumerable.Join. Fammi sapere se nessuno di questi approcci è proprio quello che sto cercando e forse posso suggerire qualcos'altro

Edit:.. Ecco un esempio di come si potrebbe utilizzare il metodo CompareSequences con la funzione di confronto che hai postato originariamente

// 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

Ciò condurrà entrambe le liste in modo sincrono ma l'oggetto potrebbe non essere ordinato allo stesso modo in entrambi gli elenchi. –

+0

Sì, certo.Non riuscivo a capire se questo fosse il caso o meno, ma in ogni caso avevo appena modificato il post prima di leggere il tuo commento, quindi ora è qualificato per la proprietà ... – Noldorin

+0

grazie a entrambi danbruc e Noldorin per il tuo risposta. entrambe le liste avranno la stessa lunghezza e nello stesso ordine. quindi il metodo di cui sopra è vicino, ma puoi modificare questo metodo per passare l'enum.Current al metodo che ho postato sopra? –

1

Spero di aver risposto correttamente alla tua domanda, ma puoi farlo tu ry rapidamente con Linq. Presumo che universalmente avrai sempre una proprietà di identificazione. Basta creare un'interfaccia per garantire questo.

Se si identifica un oggetto per le stesse modifiche da una classe all'altra, si consiglia di passare a un delegato che restituisce true se i due oggetti hanno lo stesso ID persistente.

Ecco come farlo in 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 confronto id non è abbastanza completo. – aggietech

72

Questa soluzione produce un elenco di risultati, che contiene tutte le differenze da entrambe le liste di ingresso. Puoi confrontare i tuoi oggetti con qualsiasi proprietà, nel mio esempio è ID. L'unica restrizione è che le liste devono essere dello stesso 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

Questo è fantastico. Le community ti consentono davvero di ottenere un punto di vista di altre persone su un problema. Grazie! – Jeremy

+1

Funziona perfettamente! Grazie – Haris

+0

Credo che questo presuppone che non ci siano duplicati in nessuna delle due liste? – NStuke

2

Questo approccio da Microsoft funziona molto bene e fornisce la possibilità di confrontare una lista all'altra e passare loro per ottenere la differenza di ciascuno. Se si confrontano le classi, aggiungere semplicemente gli oggetti a due elenchi separati e quindi eseguire il confronto.

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