Supprimer les doublons d'une liste <T> en C #

487

Quelqu'un a-t-il une méthode rapide pour dédupliquer une liste générique en C #?

JC Grubbs
la source
4
Vous vous souciez de l'ordre des éléments dans le résultat? Cela exclura certaines solutions.
Colonel Panic
Une solution en ligne:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse

Réponses:

227

Vous devriez peut-être envisager d'utiliser un HashSet .

À partir du lien MSDN:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        HashSet<int> evenNumbers = new HashSet<int>();
        HashSet<int> oddNumbers = new HashSet<int>();

        for (int i = 0; i < 5; i++)
        {
            // Populate numbers with just even numbers.
            evenNumbers.Add(i * 2);

            // Populate oddNumbers with just odd numbers.
            oddNumbers.Add((i * 2) + 1);
        }

        Console.Write("evenNumbers contains {0} elements: ", evenNumbers.Count);
        DisplaySet(evenNumbers);

        Console.Write("oddNumbers contains {0} elements: ", oddNumbers.Count);
        DisplaySet(oddNumbers);

        // Create a new HashSet populated with even numbers.
        HashSet<int> numbers = new HashSet<int>(evenNumbers);
        Console.WriteLine("numbers UnionWith oddNumbers...");
        numbers.UnionWith(oddNumbers);

        Console.Write("numbers contains {0} elements: ", numbers.Count);
        DisplaySet(numbers);
    }

    private static void DisplaySet(HashSet<int> set)
    {
        Console.Write("{");
        foreach (int i in set)
        {
            Console.Write(" {0}", i);
        }
        Console.WriteLine(" }");
    }
}

/* This example produces output similar to the following:
 * evenNumbers contains 5 elements: { 0 2 4 6 8 }
 * oddNumbers contains 5 elements: { 1 3 5 7 9 }
 * numbers UnionWith oddNumbers...
 * numbers contains 10 elements: { 0 2 4 6 8 1 3 5 7 9 }
 */
Jason Baker
la source
11
son incroyable rapidité ... 100 000 cordes avec List prend 400 s et 8 Mo de RAM, ma propre solution prend 2,5 s et 28 Mo, le hachage prend 0,1 s !!! et 11 Mo de RAM
sasjaq
3
HashSet n'a pas d'index , il n'est donc pas toujours possible de l'utiliser. Je dois créer une fois une énorme liste sans doublons, puis l'utiliser pour ListViewle mode virtuel. Il était super rapide de faire une HashSet<>première, puis de la convertir en un List<>(donc ListViewon peut accéder aux éléments par index). List<>.Contains()est trop lent.
Sinatr
58
Serait utile s'il y avait un exemple de la façon d'utiliser un hachage dans ce contexte particulier.
Nathan McKaskle
23
Comment cela peut-il être considéré comme une réponse? C'est un lien
mcont
2
HashSet est idéal dans la plupart des cas. Mais si vous avez un objet comme DateTime, il compare par référence et non par valeur, vous vous retrouverez donc avec des doublons.
Jason McKindly
813

Si vous utilisez .Net 3+, vous pouvez utiliser Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Facteur mystique
la source
14
Ce code échouera car .Distinct () renvoie un IEnumerable <T>. Vous devez y ajouter .ToList ().
ljs
Cette approche ne peut être utilisée que pour une liste avec des valeurs simples.
Polaris
20
Non, cela fonctionne avec des listes contenant des objets de tout type. Mais vous devrez remplacer le comparateur par défaut de votre type. Like so: public override bool Equals (object obj) {...}
BaBu
1
C'est toujours une bonne idée de remplacer ToString () et GetHashCode () avec vos classes pour que ce genre de chose fonctionne.
B Seven
2
Vous pouvez également utiliser le package MoreLinQ Nuget qui a une méthode d'extension .DistinctBy (). Assez utile.
yu_ominae
178

Que diriez-vous:

var noDupes = list.Distinct().ToList();

Dans .net 3.5?

ljs
la source
Reproduit-il la liste?
darkgaze
1
@darkgaze cela crée juste une autre liste avec seulement des entrées uniques. Ainsi, tous les doublons seront supprimés et vous vous retrouvez avec une liste où chaque position a un objet différent.
hexagod
Est-ce que cela fonctionne pour la liste de la liste des éléments de la liste où les codes d'article sont en double et doit obtenir une liste unique
venkat
90

Initialisez simplement un HashSet avec une liste du même type:

var noDupes = new HashSet<T>(withDupes);

Ou, si vous souhaitez renvoyer une liste:

var noDupsList = new HashSet<T>(withDupes).ToList();
Même Mien
la source
3
... et si vous avez besoin d'une List<T>utilisation en conséquencenew HashSet<T>(withDupes).ToList()
Tim Schmelter
47

Triez-le, puis cochez deux et deux côte à côte, car les doublons s'agglutineront.

Quelque chose comme ça:

list.Sort();
Int32 index = list.Count - 1;
while (index > 0)
{
    if (list[index] == list[index - 1])
    {
        if (index < list.Count - 1)
            (list[index], list[list.Count - 1]) = (list[list.Count - 1], list[index]);
        list.RemoveAt(list.Count - 1);
        index--;
    }
    else
        index--;
}

Remarques:

  • La comparaison se fait de l'arrière vers l'avant, pour éviter d'avoir à recourir à la liste après chaque suppression
  • Cet exemple utilise maintenant des tuples de valeur C # pour effectuer l'échange, remplacez-le par le code approprié si vous ne pouvez pas l'utiliser.
  • Le résultat final n'est plus trié
Lasse V. Karlsen
la source
1
Si je ne me trompe pas, la plupart des approches mentionnées ci-dessus ne sont que des abstractions de ces mêmes routines, non? J'aurais pris votre approche ici, Lasse, parce que c'est comme ça que j'imagine mentalement se déplacer dans les données. Mais maintenant, je m'intéresse aux différences de performances entre certaines des suggestions.
Ian Patrick Hughes
7
Mettez-les en œuvre et chronométrez-les, seul moyen d'en être sûr. Même la notation Big-O ne vous aidera pas avec les mesures de performances réelles, seulement une relation d'effet de croissance.
Lasse V. Karlsen
1
J'aime cette approche, elle est plus portable dans d'autres langues.
Jerry Liang
10
Ne fais pas ça. C'est super lent. RemoveAtest une opération très coûteuse sur unList
Clément
1
Clément a raison. Un moyen de récupérer cela serait de l'envelopper dans une méthode qui donne un énumérateur et ne renvoie que des valeurs distinctes. Vous pouvez également copier des valeurs dans un nouveau tableau ou une nouvelle liste.
JHubbard80
33

J'aime utiliser cette commande:

List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
                                                 .GroupBy(s => s.City)
                                                 .Select(grp => grp.FirstOrDefault())
                                                 .OrderBy(s => s.City)
                                                 .ToList();

J'ai ces champs dans ma liste: Id, StoreName, City, PostalCode Je voulais afficher la liste des villes dans une liste déroulante qui a des valeurs en double. solution: Groupez par ville puis choisissez le premier pour la liste.

J'espère que ça aide :)

Eric
la source
31

Ça a marché pour moi. utilisez simplement

List<Type> liIDs = liIDs.Distinct().ToList<Type>();

Remplacez "Type" par le type souhaité, par exemple int.

Hossein Sarshar
la source
1
Distinct se trouve dans Linq, pas System.Collections.Generic comme indiqué par la page MSDN.
Almo
5
Cette réponse (2012) semble être la même que deux autres réponses sur cette page qui datent de 2008?
Jon Schneider
23

Comme l'a dit kronoz dans .Net 3.5, vous pouvez utiliser Distinct() .

Dans .Net 2, vous pouvez l'imiter:

public IEnumerable<T> DedupCollection<T> (IEnumerable<T> input) 
{
    var passedValues = new HashSet<T>();

    // Relatively simple dupe check alg used as example
    foreach(T item in input)
        if(passedValues.Add(item)) // True if item is new
            yield return item;
}

Cela pourrait être utilisé pour dédupliquer n'importe quelle collection et retournera les valeurs dans l'ordre d'origine.

Il est normalement beaucoup plus rapide de filtrer une collection (comme le font les deux Distinct()et cet exemple) que de supprimer des éléments de celle-ci.

Keith
la source
Le problème avec cette approche est cependant que c'est O (N ^ 2) -ish, par opposition à un hachage. Mais au moins, c'est évident ce qu'il fait.
Tamas Czinege
1
@DrJokepu - en fait, je ne savais pas que le HashSetconstructeur avait détruit, ce qui le rend meilleur dans la plupart des cas. Cependant, cela conserverait l'ordre de tri, ce qui HashSetn'est pas le cas.
Keith
1
HashSet <T> a été introduit dans la version 3.5
thorn̈
1
@ épine vraiment? Tellement difficile de garder une trace. Dans ce cas, vous pouvez simplement utiliser un à la Dictionary<T, object>place, remplacer .Containspar .ContainsKeyet .Add(item)avec.Add(item, null)
Keith
@Keith, selon mes tests, HashSetpréserve l'ordre alors Distinct()que non.
Dennis T --Reinstate Monica--
13

Une méthode d'extension pourrait être une bonne façon de procéder ... quelque chose comme ceci:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
    return listToDeduplicate.Distinct().ToList();
}

Et puis appelez comme ceci, par exemple:

List<int> myFilteredList = unfilteredList.Deduplicate();
Geoff Taylor
la source
11

En Java (je suppose que C # est plus ou moins identique):

list = new ArrayList<T>(new HashSet<T>(list))

Si vous vouliez vraiment muter la liste originale:

List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);

Pour préserver l'ordre, remplacez simplement HashSet par LinkedHashSet.

Tom Hawtin - sellerie
la source
5
en C # ce serait: List <T> noDupes = new List <T> (new HashSet <T> (list)); list.Clear (); list.AddRange (noDupes);
smohamed
En C #, c'est plus simple de cette façon: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal
10

Cela prend des éléments distincts (les éléments sans éléments en double) et les convertit à nouveau en liste:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Alfred Udah
la source
9

Utilisez la méthode Union de Linq .

Remarque: Cette solution ne nécessite aucune connaissance de Linq, à part qu'elle existe.

Code

Commencez par ajouter ce qui suit en haut de votre fichier de classe:

using System.Linq;

Maintenant, vous pouvez utiliser ce qui suit pour supprimer les doublons d'un objet appelé obj1:

obj1 = obj1.Union(obj1).ToList();

Remarque: Renommez obj1le nom de votre objet.

Comment ça fonctionne

  1. La commande Union répertorie une entrée de chaque entrée de deux objets source. Étant donné que obj1 est les deux objets source, cela réduit obj1 à l'une de chaque entrée.

  2. La ToList()renvoie une nouvelle liste. Cela est nécessaire, car les commandes Linq comme Unionrenvoie le résultat en tant que résultat IEnumerable au lieu de modifier la liste d'origine ou de renvoyer une nouvelle liste.

WonderWorker
la source
7

Comme méthode d'assistance (sans Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}
Subvention
la source
Je pense que Distinct est déjà pris. En dehors de cela (si vous renommez la méthode), cela devrait fonctionner.
Andreas Reiff
6

Si vous ne se soucient pas de l'ordre que vous pouvez juste pousser les éléments dans un HashSet, si vous ne voulez maintenir l'ordre que vous pouvez faire quelque chose comme ceci:

var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
    if (hs.Add(t))
        unique.Add(t);

Ou à la manière Linq:

var hs = new HashSet<T>();
list.All( x =>  hs.Add(x) );

Edit: La HashSetméthode est le O(N)temps et l' O(N)espace tout en triant puis en rendant unique (comme suggéré par @ lassevk et d'autres) est le O(N*lgN)temps et l' O(1)espace donc il n'est pas si clair pour moi (comme c'était à première vue) que le mode de tri est inférieur (mon excuses pour le vote temporaire de baisse ...)

Motti
la source
6

Voici une méthode d'extension pour supprimer les doublons adjacents in situ. Appelez d'abord Sort () et passez le même IComparer. Cela devrait être plus efficace que la version de Lasse V. Karlsen qui appelle RemoveAt à plusieurs reprises (entraînant plusieurs mouvements de mémoire de bloc).

public static void RemoveAdjacentDuplicates<T>(this List<T> List, IComparer<T> Comparer)
{
    int NumUnique = 0;
    for (int i = 0; i < List.Count; i++)
        if ((i == 0) || (Comparer.Compare(List[NumUnique - 1], List[i]) != 0))
            List[NumUnique++] = List[i];
    List.RemoveRange(NumUnique, List.Count - NumUnique);
}
gary
la source
5

En installant le package MoreLINQ via Nuget, vous pouvez facilement distinguer la liste d'objets par une propriété

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 
dush88c
la source
3

Il pourrait être plus facile de simplement s'assurer que les doublons ne sont pas ajoutés à la liste.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)
Chris
la source
1
Je le fais actuellement comme ça, mais plus vous avez d'entrées, plus la vérification des doublons est longue.
Robert Strauch
J'ai le même problème ici. J'utilise la List<T>.Containsméthode à chaque fois mais avec plus de 1 000 000 d'entrées. Ce processus ralentit ma candidature. J'utilise une List<T>.Distinct().ToList<T>()première à la place.
RPDeshaies
Cette méthode est très lente
darkgaze
3

Vous pouvez utiliser Union

obj2 = obj1.Union(obj1).ToList();
flagamba
la source
7
Explication pourquoi cela fonctionnerait serait certainement mieux faire cette réponse
Igor B
2

Une autre façon dans .Net 2.0

    static void Main(string[] args)
    {
        List<string> alpha = new List<string>();

        for(char a = 'a'; a <= 'd'; a++)
        {
            alpha.Add(a.ToString());
            alpha.Add(a.ToString());
        }

        Console.WriteLine("Data :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t); });

        alpha.ForEach(delegate (string v)
                          {
                              if (alpha.FindAll(delegate(string t) { return t == v; }).Count > 1)
                                  alpha.Remove(v);
                          });

        Console.WriteLine("Unique Result :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t);});
        Console.ReadKey();
    }
Bhasin
la source
2

Il existe de nombreuses façons de résoudre le problème des doublons dans la liste ci-dessous:

List<Container> containerList = LoadContainer();//Assume it has duplicates
List<Container> filteredList = new  List<Container>();
foreach (var container in containerList)
{ 
  Container duplicateContainer = containerList.Find(delegate(Container checkContainer)
  { return (checkContainer.UniqueId == container.UniqueId); });
   //Assume 'UniqueId' is the property of the Container class on which u r making a search

    if(!containerList.Contains(duplicateContainer) //Add object when not found in the new class object
      {
        filteredList.Add(container);
       }
  }

Bravo Ravi Ganesan

Ravi Ganesan
la source
2

Voici une solution simple qui ne nécessite aucun LINQ difficile à lire ni aucun tri préalable de la liste.

   private static void CheckForDuplicateItems(List<string> items)
    {
        if (items == null ||
            items.Count == 0)
            return;

        for (int outerIndex = 0; outerIndex < items.Count; outerIndex++)
        {
            for (int innerIndex = 0; innerIndex < items.Count; innerIndex++)
            {
                if (innerIndex == outerIndex) continue;
                if (items[outerIndex].Equals(items[innerIndex]))
                {
                    // Duplicate Found
                }
            }
        }
    }
David J.
la source
Vous avez plus de contrôle sur les éléments dupliqués avec cette méthode. Encore plus si vous avez une base de données à mettre à jour. Pour le innerIndex, pourquoi ne pas partir de externalIndex + 1 au lieu de commencer à chaque fois?
Nolmë Informatique
2

La réponse de David J. est une bonne méthode, pas besoin d'objets supplémentaires, de tri, etc. Elle peut cependant être améliorée:

for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)

Ainsi, la boucle externe va en haut en bas pour toute la liste, mais la boucle interne va en bas "jusqu'à ce que la position de la boucle externe soit atteinte".

La boucle externe s'assure que toute la liste est traitée, la boucle interne trouve les doublons réels, ceux-ci ne peuvent se produire que dans la partie que la boucle externe n'a pas encore traitée.

Ou si vous ne voulez pas faire de bas en haut pour la boucle intérieure, vous pouvez faire démarrer la boucle intérieure à externalIndex + 1.

Client
la source
2

Toutes les réponses copient des listes, ou créent une nouvelle liste, ou utilisent des fonctions lentes, ou sont tout simplement douloureusement lentes.

À ma connaissance, c'est la méthode la plus rapide et la moins chère que je connaisse (également, soutenue par un programmeur très expérimenté spécialisé dans l'optimisation physique en temps réel).

// Duplicates will be noticed after a sort O(nLogn)
list.Sort();

// Store the current and last items. Current item declaration is not really needed, and probably optimized by the compiler, but in case it's not...
int lastItem = -1;
int currItem = -1;

int size = list.Count;

// Store the index pointing to the last item we want to keep in the list
int last = size - 1;

// Travel the items from last to first O(n)
for (int i = last; i >= 0; --i)
{
    currItem = list[i];

    // If this item was the same as the previous one, we don't want it
    if (currItem == lastItem)
    {
        // Overwrite last in current place. It is a swap but we don't need the last
       list[i] = list[last];

        // Reduce the last index, we don't want that one anymore
        last--;
    }

    // A new item, we store it and continue
    else
        lastItem = currItem;
}

// We now have an unsorted list with the duplicates at the end.

// Remove the last items just once
list.RemoveRange(last + 1, size - last - 1);

// Sort again O(n logn)
list.Sort();

Le coût final est:

nlogn + n + nlogn = n + 2nlogn = O (nlogn) ce qui est plutôt sympa.

Remarque sur RemoveRange: Étant donné que nous ne pouvons pas définir le nombre de la liste et éviter d'utiliser les fonctions Remove, je ne connais pas exactement la vitesse de cette opération, mais je suppose que c'est le moyen le plus rapide.

darkgaze
la source
2

Si vous avez des classes de remorquage Productet Customerque nous voulons supprimer les éléments en double de leur liste

public class Product
{
    public int Id { get; set; }
    public string ProductName { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string CustomerName { get; set; }

}

Vous devez définir une classe générique dans le formulaire ci-dessous

public class ItemEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    private readonly PropertyInfo _propertyInfo;

    public ItemEqualityComparer(string keyItem)
    {
        _propertyInfo = typeof(T).GetProperty(keyItem, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
    }

    public bool Equals(T x, T y)
    {
        var xValue = _propertyInfo?.GetValue(x, null);
        var yValue = _propertyInfo?.GetValue(y, null);
        return xValue != null && yValue != null && xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        var propertyValue = _propertyInfo.GetValue(obj, null);
        return propertyValue == null ? 0 : propertyValue.GetHashCode();
    }
}

vous pouvez ensuite supprimer les éléments en double de votre liste.

var products = new List<Product>
            {
                new Product{ProductName = "product 1" ,Id = 1,},
                new Product{ProductName = "product 2" ,Id = 2,},
                new Product{ProductName = "product 2" ,Id = 4,},
                new Product{ProductName = "product 2" ,Id = 4,},
            };
var productList = products.Distinct(new ItemEqualityComparer<Product>(nameof(Product.Id))).ToList();

var customers = new List<Customer>
            {
                new Customer{CustomerName = "Customer 1" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
            };
var customerList = customers.Distinct(new ItemEqualityComparer<Customer>(nameof(Customer.Id))).ToList();

supprimer ce code doublons par Idsi vous voulez supprimer les doublons par d' autres biens, vous pouvez modifier nameof(YourClass.DuplicateProperty) même nameof(Customer.CustomerName)supprimer les doublons puis par la CustomerNamepropriété.

Reza Jenabi
la source
1
  public static void RemoveDuplicates<T>(IList<T> list )
  {
     if (list == null)
     {
        return;
     }
     int i = 1;
     while(i<list.Count)
     {
        int j = 0;
        bool remove = false;
        while (j < i && !remove)
        {
           if (list[i].Equals(list[j]))
           {
              remove = true;
           }
           j++;
        }
        if (remove)
        {
           list.RemoveAt(i);
        }
        else
        {
           i++;
        }
     }  
  }
Paul Richards
la source
1

Une implémentation simple et intuitive:

public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
    List<PointF> result = new List<PointF>();

    for (int i = 0; i < listPoints.Count; i++)
    {
        if (!result.Contains(listPoints[i]))
            result.Add(listPoints[i]);
        }

        return result;
    }
Moctar Haiz
la source
Cette méthode est également lente. Crée une nouvelle liste.
darkgaze