Différence entre deux listes


118

J'ai deux listes génériques remplies avec CustomsObjects.

J'ai besoin de récupérer la différence entre ces deux listes (éléments qui sont dans la première sans les éléments dans la seconde) dans une troisième.

Je pensais que l'utilisation .Except()était une bonne idée mais je ne vois pas comment l'utiliser. Aide!

c# 

Réponses:


237

L'utilisation Exceptest exactement la bonne voie à suivre. Si votre type remplace Equalset GetHashCode, ou si vous n'êtes intéressé que par l'égalité des types de référence (c'est-à-dire que deux références ne sont "égales" que si elles font référence exactement au même objet), vous pouvez simplement utiliser:

var list3 = list1.Except(list2).ToList();

Si vous avez besoin d'exprimer une idée personnalisée d'égalité, par exemple par ID, vous devrez l'implémenter IEqualityComparer<T>. Par exemple:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Puis utilisez:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Notez que cela supprimera tous les éléments en double. Si vous avez besoin de conserver les doublons, il serait probablement plus simple de créer un ensemble à partir de list2et d'utiliser quelque chose comme:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

18
En passant, j'ajouterais qu'il Excepts'agit d'une opération Set , puis la liste résultante aura des valeurs distinctes, par exemple des {'A','A','B','C'}.Except({'B','C'}) retours{'A'}
digEmAll

4
J'ai ceci: {'A', 'A', 'B', 'C'}. Sauf que ({'B', 'C'}) renvoie {'A'} qui fonctionne ... cependant {'B' , 'C'}. Sauf que ({'A', 'B', 'C'}) NE retourne PAS {'A'}
MrLister

2
La différence d'ensemble de deux ensembles est définie comme les membres du premier ensemble qui n'apparaissent pas dans le second ensemble (en citant MS). Donc, {B, C}. Sauf que ({A, B, C}) ne doit rien renvoyer car B et C sont tous les deux dans le second ensemble. Pas un bug, plus à voir avec la définition mathématique de la fonction.
Steve Hibbert

Donc @JonSkeet si je veux comparer deux listes sur la base de disons 2 propriétés, je peux écrire de cette manière un comparateur et obtenir les éléments qui ne sont pas égaux, n'est-ce pas?
Ehsan Sajjad

1
@NetMage: L'OP a déclaré vouloir des "éléments qui sont dans le premier sans les éléments dans le second" - cela me semble être une différence fixe. Si la première liste contient {5, 5, 5, 5, 1} et la deuxième liste contient {5}, alors seulement 1 est dans la première liste mais pas dans la seconde.
Jon Skeet

74

Vous pouvez faire quelque chose comme ceci:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

cela m'a sauvé une boucle foreach.
alice7

12
L'un de ces "l.une propriété" ne devrait-il pas être "p.someproperty"?
Manos Dilaverakis

6
Cela peut être simplifié avec customlist.Where (p => otherlist.Any (l => p.someproperty! = L.someproperty));
Dhanuka777

@ Dhanuka777 c'est incorrect. Le Anydevrait être un Allsi vous voulez le faire comme ça. C'est parce que la deuxième liste pourrait avoir l'élément de la première liste, mais si ce n'est pas la première cochée, elle obtiendra immédiatement un vrai pour p.someproperty != l.someproperty. Cela entraîne des éléments renvoyés qui existent dans les deux listes. Honte aux 6 personnes qui ont voté pour cela.
Murphybro2

26

Je pense qu'il est important de souligner - l'utilisation de la méthode Except vous retournera les éléments qui se trouvent dans le premier sans les éléments du second uniquement. Il ne renvoie pas les éléments en second qui n'apparaissent pas en premier.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

Mais si vous voulez obtenir une réelle différence entre deux listes:

Les éléments qui sont dans le premier sans les éléments dans le second et les éléments qui sont dans le second sans les éléments dans le premier.

Vous devez utiliser sauf deux fois:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Ou vous pouvez utiliser la méthode SymmetricExceptWith de HashSet. Mais cela change l'ensemble sur lequel appelé:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

Cela est utile. Sachez simplement que l'OP a clairement indiqué qu'il ne voulait pas de différence totale: "Je dois récupérer la différence entre ces deux listes (les éléments qui sont dans la première sans les éléments de la seconde )".
rsenna le

10
var third = first.Except(second);

(vous pouvez également appeler ToList()après Except(), si vous n'aimez pas faire référence à des collections paresseuses.)

Le Except()procédé compare les valeurs à l' aide du comparateur par défaut, si les valeurs comparées sont de types de données de base, tels que int, string, decimaletc.

Sinon, la comparaison se fera par adresse d'objet, ce qui n'est probablement pas ce que vous souhaitez ... Dans ce cas, faites implémenter vos objets personnalisés IComparable(ou implémentez un personnalisé IEqualityCompareret passez-le à la Except()méthode).


3
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Remarque: list3contiendra les éléments ou objets qui ne figurent pas dans les deux listes. Remarque: ce ToList()n'est pastoList()


1

Puisque la méthode d'extension Except fonctionne sur deux IEumerables, il me semble que ce sera une opération O (n ^ 2). Si les performances sont un problème (si vos listes sont volumineuses), je suggère de créer un HashSet à partir de list1 et d'utiliser la méthode ExceptWith de HashSet.


9
Enumerable.Exceptutilise en interne HashSetou quelque chose de similaire. Il n'utilise certainement pas l'algorithme naïf O (n ^ 2).
Jim Mischel le

@Jim Mischel: Vous avez raison, Enumerable.Except utilise une structure de données Set interne et ajoute des éléments des deux IEnumerables dans l'ensemble. J'aimerais que la documentation en dise quelque chose.
foson le

1

voici ma solution:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

un peu tard mais voici une solution de travail pour moi

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

Dans le code de mention ci-dessus, j'obtiens la différence de propriétés entre ma classe de base et ma liste de classes dérivée


-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

Remarque: la liste de résultats correspond aux éléments qui existent dans la liste de contrôle mais pas dans ma liste
Teezy7

-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

6 ans après la réponse acceptée et n'ajoute rien de nouveau de valeur
Mitch Wheat

-3

Si vos deux listes implémentent l'interface IEnumerable, vous pouvez y parvenir en utilisant LINQ.

list3 = list1.where(i => !list2.contains(i));

6
Et si vos listes contiennent chacune un million d'éléments, vous allez attendre longtemps. Utilisez IEnumerable.Exceptplutôt.
Jim Mischel

3
Oui. Je n'ai qu'environ 450 articles et j'attends toujours. Soyez prudent en utilisant ceci.
Marnee KG7SIO

-3
        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.