Le moyen le plus rapide de comparer deux listes génériques pour les différences

214

Quel est le plus rapide (et le moins gourmand en ressources) pour comparer deux énormes (> 50 000 articles) et, par conséquent, avoir deux listes comme celles ci-dessous:

  1. les éléments qui apparaissent dans la première liste mais pas dans la seconde
  2. les éléments qui apparaissent dans la deuxième liste mais pas dans la première

Actuellement, je travaille avec List ou IReadOnlyCollection et je résous ce problème dans une requête linq:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

Mais cela ne fonctionne pas aussi bien que je le souhaiterais. Une idée de rendre cela plus rapide et moins gourmand en ressources car j'ai besoin de traiter un grand nombre de listes?

Franc
la source

Réponses:

454

Utilisation Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

Je soupçonne qu'il existe des approches qui seraient en fait légèrement plus rapides que cela, mais même cela sera beaucoup plus rapide que votre approche O (N * M).

Si vous souhaitez les combiner, vous pouvez créer une méthode avec ce qui précède, puis une instruction de retour:

return !firstNotSecond.Any() && !secondNotFirst.Any();

Un point à noter est qu'il est une différence dans les résultats entre le code d' origine dans la question et la solution ici: tous les éléments en double qui ne sont que dans une liste ne seront signalés une fois avec mon code, alors qu'ils seraient signalés comme beaucoup fois qu'ils se produisent dans le code d'origine.

Par exemple, avec les listes de [1, 2, 2, 2, 3]et [1], le résultat "éléments dans list1 mais pas list2" dans le code d'origine serait [2, 2, 2, 3]. Avec mon code, ce serait juste [2, 3]. Dans de nombreux cas, ce ne sera pas un problème, mais cela vaut la peine d'être conscient.

Jon Skeet
la source
8
C'est vraiment un énorme gain de performances! Merci pour cette réponse.
Frank
2
Je me demande deux énormes listes, est-il utile de trier avant de comparer? ou à l'intérieur de la méthode d'extension Sauf, la liste transmise est déjà triée.
Larry
9
@Larry: Ce n'est pas trié; il construit un ensemble de hachage.
Jon Skeet
2
@PranavSingh: Cela fonctionnera pour tout ce qui a une égalité appropriée - donc si votre type personnalisé remplace Equals(object)et / ou implémente, IEquatable<T>cela devrait aller.
Jon Skeet
2
@ k2ibegin: il utilise le comparateur d'égalité par défaut, qui utilisera une IEquatable<T>implémentation ou la object.Equals(object)méthode. Il semble que vous devriez créer une nouvelle question avec un exemple reproductible minimal - nous ne pouvons pas vraiment diagnostiquer les choses dans les commentaires.
Jon Skeet
40

Plus efficace serait d'utiliser Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

Cette méthode est implémentée en utilisant une exécution différée. Cela signifie que vous pourriez écrire par exemple:

var first10 = inListButNotInList2.Take(10);

Il est également efficace car il utilise en interne un Set<T>pour comparer les objets. Il fonctionne en collectant d'abord toutes les valeurs distinctes de la deuxième séquence, puis en diffusant les résultats de la première, en vérifiant qu'ils n'ont jamais été vus auparavant.

Tim Schmelter
la source
1
Hmm. Pas tout à fait différé. Je dirais partiellement différé. Un complet Set<T>est construit à partir de la deuxième séquence (c'est-à-dire qu'il est entièrement itéré et stocké), puis les éléments qui peuvent être ajoutés à partir de la première séquence sont générés.
dépensé
2
@spender, c'est comme dire que l'exécution de Whereest partiellement différée car dans list.Where(x => x.Id == 5)la valeur du nombre 5est stockée au début, plutôt qu'exécutée paresseusement.
jwg
27

Enumerable.SequenceEqual, méthode

Détermine si deux séquences sont égales selon un comparateur d'égalité. MS.Docs

Enumerable.SequenceEqual(list1, list2);

Cela fonctionne pour tous les types de données primitifs. Si vous devez l'utiliser sur des objets personnalisés, vous devez l'implémenterIEqualityComparer

Définit des méthodes pour prendre en charge la comparaison d'objets pour l'égalité.

Interface IEqualityComparer

Définit des méthodes pour prendre en charge la comparaison d'objets pour l'égalité. MS.Docs pour IEqualityComparer

miguelmpn
la source
ce devrait être la réponse acceptée. La question ne concerne pas les ENSEMBLES mais les LISTES, qui peuvent contenir une duplication d'éléments.
Adrian Nasui
3
Je ne vois pas comment cela pourrait être la réponse, étant donné que le résultat SequenceEqualest simple bool. L'OP veut deux listes de résultats - et décrit ce qu'ils veulent en termes d'opérations d'ensemble: "les éléments qui apparaissent dans la première liste mais pas dans la seconde". Il n'y a aucune indication que la commande est pertinente, alors que SequenceEqual ne juge pas pertinent. Cela semble répondre à une question entièrement différente.
Jon Skeet
oui, correct, il semble que j'ai répondu à celle-ci trop rapidement et que je n'ai pas regardé la deuxième partie de la demande ... comme les deux premiers commentaires ...
miguelmpn
9

Si vous souhaitez que les résultats ne respectent pas la casse , les éléments suivants fonctionneront:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondcontiendrait b1.dll

secondNotFirstcontiendrait b2.dll

par exemple
la source
5

Pas pour ce problème, mais voici du code pour comparer les listes pour égal et non! objets identiques:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}
Ermite de Pie
la source
1
Voici ce dont vous avez besoin pour pouvoir comparer des types de données personnalisés. Ensuite, utilisezExcept
Pranav Singh
Vous pouvez probablement faire mieux avec les types triables. Cela s'exécute dans O (n ^ 2), tandis que vous pouvez faire O (nlogn).
yuvalm2
3

essayez de cette façon:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
Ali Issa
la source
13
Cela souffre de performances horribles, nécessitant une analyse de la deuxième liste pour chaque élément du premier. Pas de downvoting parce que cela fonctionne, mais c'est aussi mauvais que le code d'origine.
dépensé
3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

Parfois, il suffit de savoir si deux listes sont différentes et non pas quelles sont ces différences. Dans ce cas, envisagez d'ajouter cette méthode d'extension à votre projet. Notez que vos objets listés doivent implémenter IEquatable!

Usage:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Quelle que soit la Componentclasse, les méthodes présentées ici Cardoivent être implémentées de manière presque identique.

Il est très important de noter comment nous avons écrit GetHashCode. Afin d'implémenter correctement IEquatable, Equalset GetHashCode doit fonctionner sur les propriétés de l'instance d'une manière logiquement compatible.

Deux listes avec le même contenu sont toujours des objets différents et produiront des codes de hachage différents. Puisque nous voulons que ces deux listes soient traitées comme égales, nous devons laisser GetHashCodeproduire la même valeur pour chacune d'elles. Nous pouvons accomplir cela en déléguant le code de hachage à chaque élément de la liste et en utilisant le XOR au niveau du bit standard pour les combiner tous. XOR est indépendant de l'ordre, donc peu importe si les listes sont triées différemment. Il importe seulement qu'ils ne contiennent que des membres équivalents.

Remarque: le nom étrange signifie que la méthode ne prend pas en compte l'ordre des éléments dans la liste. Si vous vous souciez de l'ordre des éléments dans la liste, cette méthode n'est pas pour vous!

Devon Parsons
la source
1

J'ai utilisé ce code pour comparer deux listes contenant des millions d'enregistrements.

Cette méthode ne prendra pas beaucoup de temps

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }
Sathish
la source
0

Si seul un résultat combiné est nécessaire, cela fonctionnera aussi:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

où T est le type d'élément de listes.

Ali Khaleghi Karsalari
la source
-1

C'est peut-être drôle, mais ça marche pour moi

string.Join ("", List1)! = string.Join ("", List2)

Jibz
la source
comme il est écrit ici, cela ne fonctionnerait même pas pour List <string> ou List <int>, comme par exemple les deux listes 11; 2; 3 et 1; 12; 3 seraient identiques car vous ne joignez pas les chaînes avec certains séparateur unique qui n'est pas un élément possible dans la liste. En dehors de cela, la concaténation de chaînes pour une liste avec beaucoup d'éléments est probablement un tueur de performances.
SwissCoder
@SwissCoder: Vous vous trompez, ce n'est pas un tueur performacne pour la chaîne. Si vous avez deux listes avec 50 000 chaînes (chacune de longueur 3) cet algorithme a besoin de 3 ms sur ma machine. La réponse acceptée a besoin de 7. Je pense que l'astuce est que Jibz n'a besoin que d'une comparaison de chaînes. Bien sûr, il doit ajouter un séparateur unique.
user1027167
@ user1027167: Je ne parle pas de comparer directement les chaînes (car ce n'est pas non plus la question). L'appel de la méthode .ToString () de tous les objets d'une liste de 50 000 objets peut créer une énorme chaîne, selon la façon dont elle est implémentée. Je ne pense pas que ce soit la voie à suivre. Ensuite, il est également risqué de compter sur un caractère ou une chaîne étant "unique", le code ne serait pas vraiment réutilisable comme ça.
SwissCoder
Ok c'est vrai. L'interrogateur a demandé le moyen le plus rapide sans donner le type de données de ses listes. Cette réponse est probablement le moyen le plus rapide pour le cas d'utilisation du questionneur.
user1027167
-3

Je pense que c'est un moyen simple et facile de comparer deux listes élément par élément

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)
user10915707
la source
3
Il s'agit d'une question C # et vous n'avez pas fourni de code C #.
Wai Ha Lee
1
Peut-être pourriez-vous supprimer cette réponse et la déplacer vers (par exemple) Comment comparer deux listes en python et renvoyer des correspondances ?
Wai Ha Lee du
-4

C'est la meilleure solution que vous ayez trouvée

var list3 = list1.Where(l => list2.ToList().Contains(l));
Fajoui El Mahdi
la source
1
C'est en fait très mauvais car cela crée un nouveau List<T>pour chaque élément dans list1. Le résultat est également appelé list3quand ce n'est pas un List<T>.
Wai Ha Lee