web-dev-qa-db-fra.com

Compare deux objets List <T> pour l'égalité, en ignorant l'ordre

Encore une autre question de comparaison de liste.

List<MyType> list1;
List<MyType> list2;

Je dois vérifier qu'ils ont tous les deux les mêmes éléments, quelle que soit leur position dans la liste. Chaque objet MyType peut apparaître plusieurs fois dans une liste. Existe-t-il une fonction intégrée qui vérifie cela? Et si je garantis que chaque élément apparaît une seule fois dans une liste?

EDIT: Merci les gars, mais j'ai oublié d'ajouter quelque chose, le nombre d'occurrences de chaque élément devrait être le même sur les deux listes.

221
Bruno Teixeira

Si vous voulez qu’ils soient vraiment égaux (c’est-à-dire les mêmes éléments et le même nombre de chaque élément), je pense que la solution la plus simple est de trier avant de comparer:

Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t))

Modifier:

Voici une solution qui fonctionne un peu mieux (environ dix fois plus rapidement), et ne nécessite que IEquatable, pas IComparable:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
  var cnt = new Dictionary<T, int>();
  foreach (T s in list1) {
    if (cnt.ContainsKey(s)) {
      cnt[s]++;
    } else {
      cnt.Add(s, 1);
    }
  }
  foreach (T s in list2) {
    if (cnt.ContainsKey(s)) {
      cnt[s]--;
    } else {
      return false;
    }
  }
  return cnt.Values.All(c => c == 0);
}

Edit 2:

Pour gérer tout type de données en tant que clé (par exemple, les types nullables comme Frank Tzanabetis l’a souligné), vous pouvez créer une version prenant un comparateur pour le dictionnaire:

public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
  var cnt = new Dictionary<T, int>(comparer);
  ...
272
Guffa

Comme écrit, cette question est ambiguë. La déclaration:

... ils ont tous deux les mêmes éléments, quelle que soit leur position dans la liste. Chaque objet MyType peut apparaître plusieurs fois dans une liste.

n'indique pas si vous voulez vous assurer que les deux listes ont le même ensemble d'objets ou le même ensemble distinct .

Si vous voulez vous assurer que les collections ont exactement le même ensemble de membres, quel que soit leur ordre, vous pouvez utiliser:

// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();

Si vous voulez vous assurer que deux collections ont le même ensemble distinct de membres (les doublons sont ignorés), vous pouvez utiliser:

// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();

L'utilisation des opérations définies (Intersect, Union, Except) est plus efficace que d'utiliser des méthodes telles que Contains. À mon avis, il exprime également mieux les attentes de votre requête.

EDIT: Maintenant que vous avez clarifié votre question, je peux dire que vous souhaitez utiliser le premier formulaire, car les doublons importent. Voici un exemple simple pour démontrer que vous obtenez le résultat souhaité:

var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };

// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any(); 
45
LBushkin

Si vous ne vous souciez pas du nombre d'occurrences, je l'aborderais comme ceci. L'utilisation de jeux de hachage vous donnera de meilleures performances qu'une simple itération.

var set1 = new HashSet<MyType>(list1);
var set2 = new HashSet<MyType>(list2);
return set1.SetEquals(set2);

Pour cela, vous devez remplacer .GetHashCode() et implémenter IEquatable<MyType> sur MyType.

39
recursive

En plus de la réponse de Guffa, vous pouvez utiliser cette variante pour avoir une notation plus abrégée.

public static bool ScrambledEquals<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
  var deletedItems = list1.Except(list2).Any();
  var newItems = list2.Except(list1).Any();
  return !newItems && !deletedItems;          
}
11
Thomas Luijken

En pensant que cela devrait faire ce que vous voulez:

list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));

si vous voulez qu'il soit distinct, vous pouvez le changer en:

list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count
8
Brian Genisio

C’est un problème un peu difficile qui, je pense, se réduit à: "Testez si deux listes sont des permutations l’une de l’autre".

Je crois que les solutions fournies par d’autres indiquent uniquement si les 2 listes contiennent les mêmes éléments niques. Ceci est un test nécessaire mais insuffisant, par exemple, {1, 1, 2, 3} n'est pas une permutation de {3, 3, 1, 2} bien que leurs comptes soient égaux et qu'ils contiennent les mêmes éléments distincts.

Je pense que cela devrait fonctionner, bien que ce ne soit pas le plus efficace:

static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
   if(list1.Count != list2.Count)
         return false;

   var l1 = list1.ToLookup(t => t);
   var l2 = list2.ToLookup(t => t);

   return l1.Count == l2.Count 
       && l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count()); 
}
6
Ani

Cela a fonctionné pour moi:
Si vous comparez deux listes d'objets qui dépendent d'une seule entité, comme ID, et que vous souhaitez une troisième liste correspondant à cette condition, procédez comme suit:

list3=List1.Where(n => !List2.select(n1 => n1.Id).Contains.(n.Id));

Référez-vous à: MSDN - C # Compare Deux listes d'objets

2
Suhail

J'utilise cette méthode)

public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);

public static bool CompareTwoArrays<T1, T2>(this IEnumerable<T1> array1, IEnumerable<T2> array2, CompareValue<T1, T2> compareValue)
{
    return array1.Select(item1 => array2.Any(item2 => compareValue(item1, item2))).All(search => search)
            && array2.Select(item2 => array1.Any(item1 => compareValue(item1, item2))).All(search => search);
}
0
TDG