web-dev-qa-db-fra.com

Comparez deux listes pour les différences

J'aimerais avoir des commentaires sur la meilleure façon d'écrire une fonction générique qui permettra de comparer deux listes. Les listes contiennent des objets de classe et nous aimerions parcourir une liste en recherchant le même élément dans une deuxième liste et signaler toute différence.

Nous avons déjà une méthode pour comparer les classes, nous avons donc besoin de commentaires sur la façon dont nous pouvons alimenter la méthode (illustrée ci-dessous) à partir de deux listes.

Par exemple, supposons que nous ayons une simple classe "Employé" qui possède trois propriétés, Nom, ID, Département. Nous voulons signaler les différences entre List et une autre List.

Remarque:
Les deux listes contiendront toujours le même nombre d'éléments.

Comme mentionné ci-dessus, nous avons une méthode générique que nous utilisons pour comparer deux classes, comment pouvons-nous incorporer cette méthode pour répondre aux listes, c'est-à-dire à partir d'une autre méthode, parcourir la liste et alimenter les classes en méthode générique .... mais comment trouver la classe équivalente dans la deuxième liste pour passer à la méthode ci-dessous;

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

Des suggestions ou des idées appréciées?

Edit: Cibler .NET 2.0 pour que LINQ soit hors de question.

44
pedro

.... mais comment trouver la classe équivalente dans la deuxième liste pour passer à la méthode ci-dessous;

C'est votre problème réel; vous devez avoir au moins une propriété immuable, un identifiant ou quelque chose comme ça, pour identifier les objets correspondants dans les deux listes. Si vous ne disposez pas d'une telle propriété, vous ne pouvez pas résoudre le problème sans erreurs. Vous pouvez simplement essayer de deviner les objets correspondants en recherchant des modifications minimales ou logiques.

Si vous avez une telle propriété, la solution devient vraiment simple.

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

merci à vous danbruc et Noldorin pour vos commentaires. les deux listes seront de la même longueur et dans le même ordre. la méthode ci-dessus est donc proche, mais pouvez-vous modifier cette méthode pour passer l'énumération actuelle à la méthode que j'ai publiée ci-dessus?

Maintenant je suis confus ... quel est le problème avec ça? Pourquoi pas juste ce qui suit?

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

L'appel Math.Min () peut même être omis si une longueur égale est garantie.


L'implémentation de Noldorin est bien sûr plus intelligente en raison du délégué et de l'utilisation d'énumérateurs au lieu d'utiliser ICollection.

15
Daniel Brückner

Cette solution produit une liste de résultats, qui contient toutes les différences des deux listes d'entrée. Vous pouvez comparer vos objets par n'importe quelle propriété, dans mon exemple, c'est ID. La seule restriction est que les listes doivent être du même type:

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

Je pense que vous cherchez une méthode comme celle-ci:

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

Ce n'est pas testé, mais il devrait néanmoins faire le travail. Notez que ce qui est particulièrement utile à propos de cette méthode est qu'elle est entièrement générique, c'est-à-dire qu'elle peut prendre deux séquences de types arbitraires (et différents) et retourner des objets de tout type.

Cette solution suppose bien sûr que vous souhaitez comparer le nième élément de seq1 avec le nième élément dans seq2. Si vous voulez faire correspondre les éléments dans les deux séquences en fonction d'une propriété/comparaison particulière, alors vous voudrez effectuer une sorte d'opération join (comme suggéré par danbruc en utilisant Enumerable.Join. Faites-moi savoir si aucune de ces approches n'est tout à fait ce que je recherche et je peux peut-être suggérer autre chose.

Modifier: Voici un exemple de la façon dont vous pourriez utiliser la méthode CompareSequences avec la fonction comparateur que vous avez initialement publiée.

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

Cette approche de Microsoft fonctionne très bien et offre la possibilité de comparer une liste à une autre et de les changer pour obtenir la différence dans chacune. Si vous comparez des classes, ajoutez simplement vos objets à deux listes distinctes, puis exécutez la comparaison.

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

2
user2284452

J'espère que je comprends bien votre question, mais vous pouvez le faire très rapidement avec Linq. Je suppose que universellement, vous aurez toujours une propriété Id. Créez simplement une interface pour vous en assurer.

Si la façon dont vous identifiez un objet comme étant le même change d'une classe à l'autre, je recommanderais de passer un délégué qui renvoie true si les deux objets ont le même identifiant persistant.

Voici comment le faire dans 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);
        }
1
Agrr