2010-09-28 16 views
157

estoy teniendo un List<string> como:¿Cómo obtener elementos duplicados de una lista usando LINQ?

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

que necesito para obtener los elementos duplicados en la lista en una nueva lista. Ahora estoy usando un loop anidado for para hacer esto.

El resultado list contendrá {"6","1"}.

¿Hay alguna idea para hacer esto usando LINQ o lambda expressions?

+3

Si la entrada es "1", "1", "1" ¿cuántos elementos debería haber en la lista resultante? –

+1

@Mark Bayers: la lista resultante debe contener '" 1 "," 1 "' :-) –

+0

Casi lo mismo: http://stackoverflow.com/questions/3239523/how-to-find-and-remove-duplicate -objects-in-a-collection-using-linq – nawfal

Respuesta

216
var duplicates = lst.GroupBy(s => s) 
    .SelectMany(grp => grp.Skip(1)); 

Tenga en cuenta que esto devolverá todos los duplicados, así que si sólo quiere saber qué elementos se duplica en la lista de fuentes, podría aplicar Distinct a la secuencia resultante o utilizar la solución dada por Mark Byers.

+4

lst.GroupBy (s => s.ToUpper()). SelectMany (grp => grp.Skip (1)); Si desea hacer una comparación insensible a las mayúsculas y minúsculas :) –

+1

@JohnJB - Hay una sobrecarga de 'GroupBy' que le permite suministrar un' IEqualityComparer' en lugar de usar 'ToUpper' para hacer una comparación insensible a mayúsculas y minúsculas. – Lee

+0

Saltar (1) es omitir el primer elemento :(¿Sabe qué debo hacer si quiero todos los elementos? – ParPar

158

Aquí es una manera de hacerlo:

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

Los grupos GroupBy los elementos que son iguales entre sí, y la Where los filtros a aquellos que sólo aparece una vez, dejándole con sólo los duplicados.

+0

Explicación simple y clara, gracias! – greenfeet

+0

manera perfecta y más fácil – NMathur

+0

No proporciona el resultado exacto como se le preguntó en cuestión, pero será útil en la mayoría de los otros casos. – Heiner

9

Esperanza esta Wil ayudar

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); 
36

Aquí hay otra opción:

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)); 
+0

¿No creo que al infractor le importaría explicar qué pasa con esta respuesta? – LukeH

+2

Haha, +1 para la innovación :) No solo eso, esto da exactamente lo que quiere el OP. La clave aquí es que puede dar una respuesta incorrecta si la consulta se enumera por segunda vez (para evitarlo, debe borrar el conjunto o inicializar uno nuevo cada vez). – nawfal

+0

O simplemente toque '.ToList()' al final de la construcción 'duplicates'. – Miral

10
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); 

    } 
18

Escribí este método de extensión con sede fuera @ la respuesta de Lee a la OP. Nota, se utilizó un parámetro predeterminado (que requiere C# 4.0). Sin embargo, una llamada al método sobrecargado en C# 3.0 sería suficiente.

/// <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; 
} 
2

yo estaba tratando de resolver el mismo con una lista de los objetos y estaba teniendo problemas porque estaba tratando de volver a embalar la lista de grupos en la lista original. Así que se me ocurrió pasar por los grupos para volver a empaquetar la Lista original con los elementos que tienen duplicados.

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; 
} 
21

Sé que no es la respuesta a la pregunta original, pero puede que te encuentres aquí con este problema.

Si desea que todos los elementos duplicados en sus resultados, lo siguiente funciona.

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 

En mi situación necesito todos los duplicados para poder marcarlos en la UI como si fueran errores.

0

Todas las soluciones mencionadas hasta ahora realizan un GroupBy. Incluso si solo necesito el primer duplicado, todos los elementos de las colecciones se enumeran al menos una vez.

La siguiente función de extensión deja de enumerar tan pronto como se encuentra un duplicado. Continúa si se solicita un próximo duplicado.

Como siempre en LINQ hay dos versiones, una con IEqualityComparer y otra sin ella.

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; 
     } 
    } 
} 

Uso:

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); 

Por todas estas declaraciones linq la colección sólo se analiza hasta que se encuentren los elementos solicitados. El resto de la secuencia no se interpreta.

en mi humilde opinión que es un impulso de eficiencia a considerar.

Cuestiones relacionadas