C # LINQ encuentra duplicados en la lista

335

Usando LINQ, desde a List<int>, ¿cómo puedo recuperar una lista que contiene entradas repetidas más de una vez y sus valores?

Mirko Arcese
fuente

Respuestas:

569

La forma más fácil de resolver el problema es agrupar los elementos en función de su valor y luego elegir un representante del grupo si hay más de un elemento en el grupo. En LINQ, esto se traduce en:

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .Select(y => y.Key)
              .ToList();

Si desea saber cuántas veces se repiten los elementos, puede usar:

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .Select(y => new { Element = y.Key, Counter = y.Count() })
              .ToList();

Esto devolverá un Listtipo anónimo, y cada elemento tendrá las propiedades Elementy Counter, para recuperar la información que necesita.

Y, por último, si está buscando un diccionario, puede usar

var query = lst.GroupBy(x => x)
              .Where(g => g.Count() > 1)
              .ToDictionary(x => x.Key, y => y.Count());

Esto devolverá un diccionario, con su elemento como clave, y la cantidad de veces que se repite como valor.

Salvar
fuente
Ahora es una maravilla, digamos que los int duplicados se distribuyen en n arrays int, estoy usando el diccionario y for loop para comprender qué arreglo contiene un duplicado y eliminarlo de acuerdo con una lógica de distribución, ¿hay una forma más rápida (linq preguntándose) para lograr ese resultado? Gracias de antemano por el interés.
Mirko Arcese
Estoy haciendo algo como esto: code for (int i = 0; i <duplicates.Count; i ++) {int duplicate = duplicates [i]; duplicatesLocation.Add (duplicado, nueva Lista <int> ()); for (int k = 0; k <hitsList.Length; k ++) {if (hitsList [k] .Contains (duplicate)) {duplicatesLocation.ElementAt (i) .Value.Add (k); }} // eliminar duplicados de acuerdo con algunas reglas. }code
Mirko Arcese
si desea encontrar duplicados en una lista de matrices, eche un vistazo a SelectMany
Save
Estoy buscando duplicados en una serie de listas, pero no entendí cómo selectmany puede ayudarme a lograrlo
Mirko Arcese
1
Para verificar si alguna colección tiene más de un elemento, es más eficiente usar Skip (1) .Any () en lugar de Count (). Imagina una colección con 1000 elementos. Saltar (1). Cualquier () detectará que hay más de 1 una vez que encuentre el segundo elemento. Usar Count () requiere acceder a la colección completa.
Harald Coppoolse
133

Averigüe si un enumerable contiene algún duplicado :

var anyDuplicate = enumerable.GroupBy(x => x.Key).Any(g => g.Count() > 1);

Averigüe si todos los valores en un enumerable son únicos :

var allUnique = enumerable.GroupBy(x => x.Key).All(g => g.Count() == 1);
maxbeaudoin
fuente
¿Existe alguna posibilidad de que estos no sean siempre opuestos booleanos? anyDuplicate ==! allUnique en todos los casos.
Garr Godfrey
1
@GarrGodfrey Siempre son opuestos booleanos
Caltor
21

Otra forma es usar HashSet:

var hash = new HashSet<int>();
var duplicates = list.Where(i => !hash.Add(i));

Si desea valores únicos en su lista de duplicados:

var myhash = new HashSet<int>();
var mylist = new List<int>(){1,1,2,2,3,3,3,4,4,4};
var duplicates = mylist.Where(item => !myhash.Add(item)).Distinct().ToList();

Aquí está la misma solución que un método de extensión genérico:

public static class Extensions
{
  public static IEnumerable<TSource> GetDuplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IEqualityComparer<TKey> comparer)
  {
    var hash = new HashSet<TKey>(comparer);
    return source.Where(item => !hash.Add(selector(item))).ToList();
  }

  public static IEnumerable<TSource> GetDuplicates<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
  {
    return source.GetDuplicates(x => x, comparer);      
  }

  public static IEnumerable<TSource> GetDuplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
  {
    return source.GetDuplicates(selector, null);
  }

  public static IEnumerable<TSource> GetDuplicates<TSource>(this IEnumerable<TSource> source)
  {
    return source.GetDuplicates(x => x, null);
  }
}
HuBeZa
fuente
Esto no funciona como se esperaba. Utilizando List<int> { 1, 2, 3, 4, 5, 2 }como fuente, el resultado es un IEnumerable<int>elemento con un valor de 1(donde el valor duplicado correcto es 2)
BCA
@BCA ayer, creo que te equivocas. Mira este ejemplo: dotnetfiddle.net/GUnhUl
HuBeZa
Su violín imprime el resultado correcto. Sin embargo, agregué la línea Console.WriteLine("Count: {0}", duplicates.Count());directamente debajo e imprime 6. A menos que me falte algo sobre los requisitos para esta función, solo debe haber 1 elemento en la colección resultante.
BCA
@BCA ayer, es un error causado por la ejecución diferida de LINQ. He agregado ToListpara solucionar el problema, pero significa que el método se ejecuta tan pronto como se lo llamó, y no cuando iteras sobre los resultados.
HuBeZa
var hash = new HashSet<int>(); var duplicates = list.Where(i => !hash.Add(i));conducirá a una lista que incluye todas las ocurrencias de duplicados. Entonces, si tiene cuatro ocurrencias de 2 en su lista, entonces su lista duplicada contendrá tres ocurrencias de 2, ya que solo una de las 2 se puede agregar al HashSet. Si desea que su lista contenga valores únicos para cada duplicado, use este código en su lugar:var duplicates = mylist.Where(item => !myhash.Add(item)).ToList().Distinct().ToList();
solid_luffy
10

Puedes hacerlo:

var list = new[] {1,2,3,1,4,2};
var duplicateItems = list.Duplicates();

Con estos métodos de extensión:

public static class Extensions
{
    public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
    {
        var grouped = source.GroupBy(selector);
        var moreThan1 = grouped.Where(i => i.IsMultiple());
        return moreThan1.SelectMany(i => i);
    }

    public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source)
    {
        return source.Duplicates(i => i);
    }

    public static bool IsMultiple<T>(this IEnumerable<T> source)
    {
        var enumerator = source.GetEnumerator();
        return enumerator.MoveNext() && enumerator.MoveNext();
    }
}

Usar IsMultiple () en el método Duplicados es más rápido que Count () porque esto no itera toda la colección.

Alex Siepman
fuente
Si observa la fuente de referencia para la Agrupación , puede ver que Count() está precalculada y que su solución es más lenta.
Johnbot
@Johnbot. Tiene razón, en este caso es más rápido y es probable que la implementación nunca cambie ... pero depende de un detalle de implementación de la clase de implementación detrás de IGrouping. Con mi implementación, sabes que nunca iterará toda la colección.
Alex Siepman
entonces contar [ Count()] es básicamente diferente de iterar toda la lista. Count()está precalculado pero no lo está iterando toda la lista.
Jogi
@rehan khan: No entiendo la diferencia entre Count () y Count ()
Alex Siepman
2
@RehanKhan: IsMultiple NO está haciendo un conteo (), se detiene inmediatamente después de 2 elementos. Al igual que Take (2) .Count> = 2;
Alex Siepman
6

Creé una extensión para responder a esto, podría incluirlo en sus proyectos, creo que esto devuelve la mayoría de los casos cuando busca duplicados en List o Linq.

Ejemplo:

//Dummy class to compare in list
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public Person(int id, string name, string surname)
    {
        this.Id = id;
        this.Name = name;
        this.Surname = surname;
    }
}


//The extention static class
public static class Extention
{
    public static IEnumerable<T> getMoreThanOnceRepeated<T>(this IEnumerable<T> extList, Func<T, object> groupProps) where T : class
    { //Return only the second and next reptition
        return extList
            .GroupBy(groupProps)
            .SelectMany(z => z.Skip(1)); //Skip the first occur and return all the others that repeats
    }
    public static IEnumerable<T> getAllRepeated<T>(this IEnumerable<T> extList, Func<T, object> groupProps) where T : class
    {
        //Get All the lines that has repeating
        return extList
            .GroupBy(groupProps)
            .Where(z => z.Count() > 1) //Filter only the distinct one
            .SelectMany(z => z);//All in where has to be retuned
    }
}

//how to use it:
void DuplicateExample()
{
    //Populate List
    List<Person> PersonsLst = new List<Person>(){
    new Person(1,"Ricardo","Figueiredo"), //fist Duplicate to the example
    new Person(2,"Ana","Figueiredo"),
    new Person(3,"Ricardo","Figueiredo"),//second Duplicate to the example
    new Person(4,"Margarida","Figueiredo"),
    new Person(5,"Ricardo","Figueiredo")//third Duplicate to the example
    };

    Console.WriteLine("All:");
    PersonsLst.ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        All:
        1 -> Ricardo Figueiredo
        2 -> Ana Figueiredo
        3 -> Ricardo Figueiredo
        4 -> Margarida Figueiredo
        5 -> Ricardo Figueiredo
        */

    Console.WriteLine("All lines with repeated data");
    PersonsLst.getAllRepeated(z => new { z.Name, z.Surname })
        .ToList()
        .ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        All lines with repeated data
        1 -> Ricardo Figueiredo
        3 -> Ricardo Figueiredo
        5 -> Ricardo Figueiredo
        */
    Console.WriteLine("Only Repeated more than once");
    PersonsLst.getMoreThanOnceRepeated(z => new { z.Name, z.Surname })
        .ToList()
        .ForEach(z => Console.WriteLine("{0} -> {1} {2}", z.Id, z.Name, z.Surname));
    /* OUTPUT:
        Only Repeated more than once
        3 -> Ricardo Figueiredo
        5 -> Ricardo Figueiredo
        */
}
Ricardo Figueiredo
fuente
1
Considere usar Skip (1) .Any () en lugar de Count (). Si tiene 1000 duplicados, entonces Saltar (1). Cualquier () se detendrá después de encontrar el segundo. Count () accederá a los 1000 elementos.
Harald Coppoolse
1
Si agrega este método de extensión, considere usar HashSet.Add en lugar de GroupBy, como se sugiere en una de las otras respuestas. Tan pronto como HashSet.Add encuentre un duplicado, se detendrá. Su GroupBy continuará agrupando todos los elementos, incluso si se ha encontrado un grupo con más de un elemento
Harald Coppoolse
6

Para buscar solo los valores duplicados:

var duplicates = list.GroupBy(x => x.Key).Any(g => g.Count() > 1);

P.ej. var list = new [] {1,2,3,1,4,2};

entonces group by agrupará los números por sus teclas y mantendrá la cuenta (número de veces que se repitió) con él. Después de eso, solo estamos verificando los valores que se han repetido más de una vez.

Para encontrar solo los valores únicos:

var unique = list.GroupBy(x => x.Key).All(g => g.Count() == 1);

P.ej. var list = new [] {1,2,3,1,4,2};

entonces group by agrupará los números por sus teclas y mantendrá la cuenta (número de veces que se repitió) con él. Después de eso, solo estamos verificando los valores que han repetido solo una vez que los medios son únicos.

LAV VISHWAKARMA
fuente
Debajo del código también encontrará artículos únicos. var unique = list.Distinct(x => x)
Malu MN
1

Conjunto completo de extensiones de Linq a SQL de funciones duplicadas marcadas en MS SQL Server. Sin usar .ToList () o IEnumerable. Estas consultas se ejecutan en SQL Server en lugar de en memoria. . Los resultados solo vuelven a la memoria.

public static class Linq2SqlExtensions {

    public class CountOfT<T> {
        public T Key { get; set; }
        public int Count { get; set; }
    }

    public static IQueryable<TKey> Duplicates<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(s => s.Key);

    public static IQueryable<TSource> GetDuplicates<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).SelectMany(s => s);

    public static IQueryable<CountOfT<TKey>> DuplicatesCounts<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(y => new CountOfT<TKey> { Key = y.Key, Count = y.Count() });

    public static IQueryable<Tuple<TKey, int>> DuplicatesCountsAsTuble<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> groupBy)
        => source.GroupBy(groupBy).Where(w => w.Count() > 1).Select(s => Tuple.Create(s.Key, s.Count()));
}
GeoB
fuente
0

hay una respuesta pero no entendí por qué no funciona;

var anyDuplicate = enumerable.GroupBy(x => x.Key).Any(g => g.Count() > 1);

mi solución es así en esta situación;

var duplicates = model.list
                    .GroupBy(s => s.SAME_ID)
                    .Where(g => g.Count() > 1).Count() > 0;
if(duplicates) {
    doSomething();
}
Aykut Gündoğdu
fuente