Je voudrais comparer deux collections (en C #), mais je ne suis pas sûr de la meilleure façon de l'implémenter efficacement.
J'ai lu l'autre fil de discussion sur Enumerable.SequenceEqual , mais ce n'est pas exactement ce que je recherche.
Dans mon cas, deux collections seraient égales si elles contiennent toutes les deux les mêmes éléments (quel que soit l'ordre).
Exemple:
collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};
collection1 == collection2; // true
Ce que je fais habituellement, c'est de parcourir chaque élément d'une collection et de voir s'il existe dans l'autre collection, puis de parcourir chaque élément de l'autre collection et de voir s'il existe dans la première collection. (Je commence par comparer les longueurs).
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
Cependant, ce n'est pas tout à fait correct et ce n'est probablement pas le moyen le plus efficace de comparer deux collections pour l'égalité.
Un exemple auquel je pense que ce serait faux est:
collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}
Ce qui serait égal à ma mise en œuvre. Dois-je simplement compter le nombre de fois où chaque élément est trouvé et m'assurer que les nombres sont égaux dans les deux collections?
Les exemples sont dans une sorte de C # (appelons-le pseudo-C #), mais donnez votre réponse dans la langue que vous souhaitez, cela n'a pas d'importance.
Remarque: j'ai utilisé des entiers dans les exemples par souci de simplicité, mais je veux pouvoir utiliser également des objets de type référence (ils ne se comportent pas correctement comme des clés car seule la référence de l'objet est comparée, pas le contenu).
la source
Réponses:
Il s'avère que Microsoft a déjà couvert cela dans son cadre de test: CollectionAssert.AreEquivalent
À l'aide du réflecteur, j'ai modifié le code derrière AreEquivalent () pour créer un comparateur d'égalité correspondant. Il est plus complet que les réponses existantes, car il prend en compte les valeurs nulles, implémente IEqualityComparer et offre des vérifications d'efficacité et de cas de pointe. en plus, c'est Microsoft :)
Exemple d'utilisation:
Ou si vous souhaitez simplement comparer directement deux collections:
Enfin, vous pouvez utiliser votre comparateur d'égalité de votre choix:
la source
EqualityComparer
(soit celui que vous avez fourni, soitEqualityComparer.Default
, vous pouvez vérifier Reflector ou la source de référence pour le vérifier). Certes, si les objets changent (et en particulier leur hashcode change) pendant que cette méthode est en cours d'exécution, les résultats sont inattendus, mais cela signifie simplement que cette méthode n'est pas thread-safe dans ce contexte.EqualityComparer
(ouEqualityComparer.Default
si aucun n'a été spécifié) et à nouveau l'implémentation est correcte.Equals
cause de l'IEqualityComparer<T>
interface. Ce que vous devriez regarder, c'est le nom du comparateur lui-même . Dans ce cas, c'estMultiSetComparer
ce qui a du sens.Une solution simple et assez efficace consiste à trier les deux collections puis à les comparer pour l'égalité:
Cet algorithme est O (N * logN), tandis que votre solution ci-dessus est O (N ^ 2).
Si les collections ont certaines propriétés, vous pourrez peut-être implémenter une solution plus rapide. Par exemple, si vos deux collections sont des ensembles de hachage, elles ne peuvent pas contenir de doublons. De plus, vérifier si un ensemble de hachage contient un élément est très rapide. Dans ce cas, un algorithme similaire au vôtre serait probablement le plus rapide.
la source
Créez un dictionnaire "dict", puis pour chaque membre de la première collection, faites dict [membre] ++;
Ensuite, parcourez la deuxième collection de la même manière, mais pour chaque membre, faites dict [membre] -.
À la fin, parcourez tous les membres du dictionnaire:
Edit: Autant que je sache, c'est dans le même ordre que l'algorithme le plus efficace. Cet algorithme est O (N), en supposant que le dictionnaire utilise les recherches O (1).
la source
return dict.All(kvp => kvp.Value == 0);
Voici mon implémentation générique (fortement influencée par D.Jennings) de la méthode de comparaison (en C #):
la source
The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"
- ce n'est pas vrai. L'algorithme est basé sur de fausses hypothèses et bien qu'il fonctionne, il est terriblement inefficace.Vous pouvez utiliser un Hashset . Regardez la méthode SetEquals .
la source
Si vous utilisez Shouldly , vous pouvez utiliser ShouldAllBe avec Contains.
Et enfin, vous pouvez écrire une extension.
METTRE À JOUR
Un paramètre facultatif existe sur la méthode ShouldBe .
la source
bool ignoreOrder
sur la méthode ShouldBe .EDIT: J'ai réalisé dès que j'ai posé que cela ne fonctionne vraiment que pour les ensembles - cela ne traitera pas correctement les collections qui ont des éléments en double. Par exemple, {1, 1, 2} et {2, 2, 1} seront considérés comme égaux du point de vue de cet algorithme. Si vos collections sont des ensembles (ou que leur égalité peut être mesurée de cette façon), j'espère que vous trouverez ce qui suit utile.
La solution que j'utilise est:
Linq fait le truc du dictionnaire sous les couvertures, donc c'est aussi O (N). (Notez que c'est O (1) si les collections ne sont pas de la même taille).
J'ai fait une vérification de bon sens en utilisant la méthode "SetEqual" suggérée par Daniel, la méthode OrderBy / SequenceEquals suggérée par Igor et ma suggestion. Les résultats sont ci-dessous, montrant O (N * LogN) pour Igor et O (N) pour le mien et celui de Daniel.
Je pense que la simplicité du code d'intersection de Linq en fait la solution préférable.
la source
En cas d'absence de répétition et d'ordre, le EqualityComparer suivant peut être utilisé pour autoriser les collections en tant que clés de dictionnaire:
Voici l'implémentation ToHashSet () que j'ai utilisée. L' algorithme de code de hachage provient de Effective Java (via Jon Skeet).
la source
ISet<T>
exprimer qu'elle est destinée aux ensembles (c'est-à-dire pas de doublons).ISet
, l'idée ici était de traiter leIEnumerable
comme un ensemble (parce que vous avez unIEnumerable
pour commencer), tout en considérant les 0 votes positifs en plus 5 ans qui n'ont peut-être pas été l'idée la plus pointue: PLa solution nécessite .NET 3.5 et l'
System.Collections.Generic
espace de noms. Selon Microsoft ,SymmetricExceptWith
est une opération O (n + m) , avec n représentant le nombre d'éléments dans le premier ensemble et m représentant le nombre d'éléments dans le second. Vous pouvez toujours ajouter un comparateur d'égalité à cette fonction si nécessaire.la source
Pourquoi ne pas utiliser .Except ()
http://msdn.microsoft.com/en-us/library/bb397894.aspx
la source
Except
ne fonctionnera pas pour compter les éléments en double. Il retournera vrai pour les ensembles {1,2,2} et {1,1,2}.[1, 1, 2] != [1, 2, 2]
. L'utilisationDistinct
les rendrait égaux.Un article en double, mais consultez ma solution pour comparer les collections . C'est assez simple:
Cela effectuera une comparaison d'égalité quel que soit l'ordre:
Cela vérifiera si des éléments ont été ajoutés / supprimés:
Cela verra quels éléments du dictionnaire ont changé:
Message original ici .
la source
erickson a presque raison: puisque vous voulez faire correspondre le nombre de doublons, vous voulez un sac . En Java, cela ressemble à quelque chose comme:
Je suis sûr que C # a une implémentation Set intégrée. J'utiliserais cela en premier; si les performances sont un problème, vous pouvez toujours utiliser une implémentation Set différente, mais utiliser la même interface Set.
la source
Voici ma variante de méthode d'extension de la réponse d'Ohadsc, au cas où cela serait utile à quelqu'un
la source
IEnumerable<T>
s sont des requêtes, appelerCount()
n'est pas une bonne idée. L'approche originale de la réponse d'Ohad consistant à vérifier si elles le sontICollection<T>
est la meilleure idée.Voici une solution qui est une amélioration par rapport à celle-ci .
la source
Il existe de nombreuses solutions à ce problème. Si vous ne vous souciez pas des doublons, vous n'avez pas à trier les deux. Assurez-vous d'abord qu'ils ont le même nombre d'articles. Après cela, une des collections. Recherchez ensuite chaque élément de la deuxième collection de la collection triée. Si vous ne trouvez pas un élément donné, arrêtez et renvoyez false. La complexité de ceci: - tri de la première collection: N Log (N) - recherche de chaque élément du deuxième au premier: NLOG (N) donc vous vous retrouvez avec 2 * N * LOG (N) en supposant qu'ils correspondent et vous recherchez tout. Ceci est similaire à la complexité du tri des deux. Cela vous donne également l'avantage d'arrêter plus tôt s'il y a une différence. Cependant, gardez à l'esprit que si les deux sont triés avant d'entrer dans cette comparaison et que vous essayez de trier en utilisant quelque chose comme un qsort, le tri sera plus coûteux. Il existe des optimisations pour cela. Une autre alternative, idéale pour les petites collections où vous connaissez la plage des éléments, consiste à utiliser un index de masque de bits. Cela vous donnera une performance O (n). Une autre alternative consiste à utiliser un hachage et à le rechercher. Pour les petites collections, il est généralement préférable de faire le tri ou l'index du masque de bits. Hashtable a l'inconvénient de pire localité, alors gardez cela à l'esprit. Encore une fois, ce n'est que si vous ne le faites pas t se soucier des doublons. Si vous souhaitez tenir compte des doublons, optez pour le tri des deux.
la source
Dans de nombreux cas, la seule réponse appropriée est celle d'Igor Ostrovsky, d'autres réponses sont basées sur le code de hachage d'objets. Mais lorsque vous générez un code de hachage pour un objet, vous le faites uniquement en fonction de ses champs IMMUTABLES - tels que le champ Id d'objet (dans le cas d'une entité de base de données) - Pourquoi est-il important de remplacer GetHashCode lorsque la méthode Equals est remplacée?
Cela signifie que si vous comparez deux collections, le résultat peut être vrai pour la méthode de comparaison même si les champs des différents éléments ne sont pas égaux. Pour comparer en profondeur les collections, vous devez utiliser la méthode d'Igor et implémenter IEqualirity.
Veuillez lire les commentaires de moi et de M. Schnider sur son poste le plus voté.
James
la source
En autorisant les doublons dans le
IEnumerable<T>
(si les ensembles ne sont pas souhaitables \ possible) et "ignorer l'ordre", vous devriez pouvoir utiliser un.GroupBy()
.Je ne suis pas un expert des mesures de complexité, mais ma compréhension rudimentaire est que cela devrait être O (n). Je comprends O (n ^ 2) comme venant de l'exécution d'une opération O (n) dans une autre opération O (n) comme
ListA.Where(a => ListB.Contains(a)).ToList()
. Chaque élément de ListB est évalué pour l'égalité par rapport à chaque élément de ListA.Comme je l'ai dit, ma compréhension de la complexité est limitée, alors corrigez-moi si je me trompe.
la source
Cette solution simple force l'
IEnumerable
implémentation du type générique deIComparable
. En raison deOrderBy
la définition de.Si vous ne souhaitez pas faire une telle hypothèse mais que vous souhaitez tout de même utiliser cette solution, vous pouvez utiliser le morceau de code suivant:
la source
Si vous comparez dans le but d'assertions de test unitaire, il peut être judicieux de jeter une certaine efficacité par la fenêtre et de simplement convertir chaque liste en une représentation sous forme de chaîne (csv) avant de faire la comparaison. De cette façon, le message d'assertion de test par défaut affichera les différences dans le message d'erreur.
Usage:
Méthode d'extension d'assistance:
la source