web-dev-qa-db-fra.com

Comment obtenir des éléments en double d'une liste à l'aide de LINQ?

Je vais avoir un List<string> comme:

List<String> list = new List<String>{"6","1","2","4","6","5","1"};

Je dois obtenir les éléments en double de la liste dans une nouvelle liste. J'utilise maintenant une boucle imbriquée for pour le faire.

La list résultante contiendra {"6","1"}.

Y at-il une idée de faire cela en utilisant LINQ ou expressions lambda ?

161
Thorin Oakenshield
var duplicates = lst.GroupBy(s => s)
    .SelectMany(grp => grp.Skip(1));

Notez que ceci retournera tous les doublons. Ainsi, si vous voulez seulement savoir quels éléments sont dupliqués dans la liste des sources, vous pouvez appliquer Distinct à la séquence résultante ou utiliser la solution fournie par Mark Byers.

230
Lee

Voici une façon de le faire:

List<String> duplicates = lst.GroupBy(x => x)
                             .Where(g => g.Count() > 1)
                             .Select(g => g.Key)
                             .ToList();

La GroupBy regroupe les éléments identiques, et la Where filtre ceux qui n'apparaissent qu'une seule fois, en vous laissant uniquement les doublons.

167
Mark Byers

Voici une autre option:

var list = new List<string> { "6", "1", "2", "4", "6", "5", "1" };

var set = new HashSet<string>();
var duplicates = list.Where(x => !set.Add(x));
37
LukeH

Je sais que ce n'est pas la réponse à la question initiale, mais vous pouvez vous retrouver ici avec ce problème.

Si vous souhaitez que tous les éléments en double dans vos résultats, les opérations suivantes fonctionnent.

var duplicates = list
    .GroupBy( x => x )               // group matching items
    .Where( g => g.Skip(1).Any() )   // where the group contains more than one item
    .SelectMany( g => g );           // re-expand the groups with more than one item

Dans ma situation, j'ai besoin de tous les doublons pour pouvoir les marquer dans l'interface utilisateur comme des erreurs.

22
Scott Langham

J'ai écrit cette méthode d'extension basée sur la réponse de @ Lee au PO. Remarque , un paramètre par défaut a été utilisé (nécessitant C # 4.0). Cependant, un appel de méthode surchargé en C # 3.0 suffirait. 

/// <summary>
/// Method that returns all the duplicates (distinct) in the collection.
/// </summary>
/// <typeparam name="T">The type of the collection.</typeparam>
/// <param name="source">The source collection to detect for duplicates</param>
/// <param name="distinct">Specify <b>true</b> to only return distinct elements.</param>
/// <returns>A distinct list of duplicates found in the source collection.</returns>
/// <remarks>This is an extension method to IEnumerable&lt;T&gt;</remarks>
public static IEnumerable<T> Duplicates<T>
         (this IEnumerable<T> source, bool distinct = true)
{
     if (source == null)
     {
        throw new ArgumentNullException("source");
     }

     // select the elements that are repeated
     IEnumerable<T> result = source.GroupBy(a => a).SelectMany(a => a.Skip(1));

     // distinct?
     if (distinct == true)
     {
        // deferred execution helps us here
        result = result.Distinct();
     }

     return result;
}
18
Michael

J'espère que cela aidera

int[] listOfItems = new[] { 4, 2, 3, 1, 6, 4, 3 };

var duplicates = listOfItems 
    .GroupBy(i => i)
    .Where(g => g.Count() > 1)
    .Select(g => g.Key);

foreach (var d in duplicates)
    Console.WriteLine(d);
10
Thakur
  List<String> list = new List<String> { "6", "1", "2", "4", "6", "5", "1" };

    var q = from s in list
            group s by s into g
            where g.Count() > 1
            select g.First();

    foreach (var item in q)
    {
        Console.WriteLine(item);

    }
10
explorer

J'essayais de résoudre le même problème avec une liste d'objets et je rencontrais des problèmes parce que j'essayais de reconditionner la liste des groupes dans la liste d'origine. Je suis donc venu en boucle à travers les groupes pour remballer la liste originale avec des éléments qui ont des doublons.

public List<MediaFileInfo> GetDuplicatePictures()
{
    List<MediaFileInfo> dupes = new List<MediaFileInfo>();
    var grpDupes = from f in _fileRepo
                   group f by f.Length into grps
                   where grps.Count() >1
                   select grps;
    foreach (var item in grpDupes)
    {
        foreach (var thing in item)
        {
            dupes.Add(thing);
        }
    }
    return dupes;
}
2
Jamie L.

Toutes les solutions mentionnées jusqu'à présent effectuent un GroupBy. Même si je n'ai besoin que du premier doublon, tous les éléments des collections sont énumérés au moins une fois.

La fonction d'extension suivante arrête d'énumérer dès qu'un duplicata a été trouvé. Cela continue si un prochain duplicata est demandé.

Comme toujours dans LINQ, il existe deux versions, l’une avec IEqualityComparer et l’autre sans elle.

public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource> source)
{
    return source.ExtractDuplicates(null);
}
public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource source,
    IEqualityComparer<TSource> comparer);
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (comparer == null)
        comparer = EqualityCompare<TSource>.Default;

    HashSet<TSource> foundElements = new HashSet<TSource>(comparer);
    foreach (TSource sourceItem in source)
    {
        if (!foundElements.Contains(sourceItem))
        {   // we've not seen this sourceItem before. Add to the foundElements
            foundElements.Add(sourceItem);
        }
        else
        {   // we've seen this item before. It is a duplicate!
            yield return sourceItem;
        }
    }
}

Usage:

IEnumerable<MyClass> myObjects = ...

// check if has duplicates:
bool hasDuplicates = myObjects.ExtractDuplicates().Any();

// or find the first three duplicates:
IEnumerable<MyClass> first3Duplicates = myObjects.ExtractDuplicates().Take(3)

// or find the first 5 duplicates that have a Name = "MyName"
IEnumerable<MyClass> myNameDuplicates = myObjects.ExtractDuplicates()
    .Where(duplicate => duplicate.Name == "MyName")
    .Take(5);

Pour toutes ces instructions linq, la collection est uniquement analysée jusqu'à ce que les éléments demandés soient trouvés. Le reste de la séquence n'est pas interprété.

IMHO c'est un gain d'efficacité à prendre en compte.

0
Harald Coppoolse