C # LINQ encontra duplicatas na lista

335

Usando o LINQ, de a List<int>, como recuperar uma lista que contém entradas repetidas mais de uma vez e seus valores?

Mirko Arcese
fonte

Respostas:

569

A maneira mais fácil de resolver o problema é agrupar os elementos com base em seu valor e escolher um representante do grupo se houver mais de um elemento no grupo. No LINQ, isso se traduz em:

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

Se você quiser saber quantas vezes os elementos são repetidos, você pode usar:

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

Isso retornará um Listde um tipo anônimo e cada elemento terá as propriedades Elemente Counter, para recuperar as informações necessárias.

E, finalmente, se você procura um dicionário, pode usar

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

Isso retornará um dicionário, com seu elemento como chave, e o número de vezes que ele será repetido como valor.

Salve 
fonte
Agora, é de se admirar, digamos que int duplicado seja distribuído em n matrizes int, estou usando o dicionário e o loop for para entender qual array contém um duplicado e removê-lo de acordo com uma lógica de distribuição, existe uma maneira mais rápida (linq querendo saber) alcançar esse resultado? desde já agradeço seu interesse.
Mirko Arcese
Estou fazendo algo parecido com isto: code for (int i = 0; i <duplicates.Count; i ++) {int duplicate = duplicatas [i]; duplicatesLocation.Add (duplicado, nova List <int> ()); for (int k = 0; k <hitsList.Length; k ++) {if (hitsList [k]. Contém (duplicado)) {duplicatesLocation.ElementAt (i) .Value.Add (k); }} // remove duplicatas de acordo com algumas regras. }code
Mirko Arcese em 31/08/13
se você deseja encontrar duplicatas em uma lista de matrizes, dê uma olhada em SelectMany
Salvar
Estou à procura de duplicatas em uma matriz de listas, mas não obter como SelectMany pode me ajudar a fazê-lo fora
Mirko Arcese
1
Para verificar se alguma coleção tem mais de um elemento, se é mais eficiente usar Skip (1) .Any () em vez de Count (). Imagine uma coleção com 1000 elementos. Skip (1). Qualquer () detectará que há mais de 1 quando encontrar o 2º elemento. O uso de Count () requer acesso à coleção completa.
Harald Coppoolse 26/10
133

Descubra se um enumerável contém alguma duplicata :

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

Descubra se todos os valores em um enumerável são únicos :

var allUnique = enumerable.GroupBy(x => x.Key).All(g => g.Count() == 1);
maxbeaudoin
fonte
Existe alguma possibilidade de que esses nem sempre sejam opostos booleanos? anyDuplicate ==! allUnique em todos os casos.
Garr Godfrey
1
@GarrGodfrey Eles são sempre opostos boolean
Caltor
21

Outra maneira é usar HashSet:

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

Se você deseja valores únicos na sua lista de duplicatas:

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

Aqui está a mesma solução que um método de extensão 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
fonte
Isso não funciona conforme o esperado. Utilizando List<int> { 1, 2, 3, 4, 5, 2 }como fonte, o resultado é um IEnumerable<int>com um elemento com o valor de 1(onde o valor duplicado correto é 2) #
181 BCA
@BCA ontem, acho que você está errado. Confira este exemplo: dotnetfiddle.net/GUnhUl
HuBeZa 15/01
Seu violino imprime o resultado correto. No entanto, adicionei a linha Console.WriteLine("Count: {0}", duplicates.Count());diretamente abaixo e ela imprime 6. A menos que esteja faltando algo sobre os requisitos para esta função, deve haver apenas 1 item na coleção resultante.
BCA 16/01
@BCA ontem, é um bug causado pela execução adiada do LINQ. Eu adicionei ToListpara corrigir o problema, mas isso significa que o método é executado assim que chamado, e não quando você itera os resultados.
HuBeZa
var hash = new HashSet<int>(); var duplicates = list.Where(i => !hash.Add(i));levará a uma lista que inclui todas as ocorrências de duplicatas. Portanto, se você tiver quatro ocorrências de 2 em sua lista, sua lista duplicada conterá três ocorrências de 2, pois apenas uma das 2 pode ser adicionada ao HashSet. Se você deseja que sua lista contenha valores exclusivos para cada duplicata, use este código:var duplicates = mylist.Where(item => !myhash.Add(item)).ToList().Distinct().ToList();
solid_luffy
10

Você consegue fazer isso:

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

Com estes métodos de extensão:

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 () no método Duplicates é mais rápido que Count () porque isso não itera a coleção inteira.

Alex Siepman
fonte
Se você olhar para a fonte de referência do Agrupamento, poderá ver que Count() é pré-calculado e sua solução provavelmente é mais lenta.
Johnbot
@Johnbot. Você está certo, neste caso, é mais rápido e a implementação provavelmente nunca muda ... mas depende de um detalhe da implementação da classe de implementação por trás do IGrouping. Com minha implementação, você sabe que nunca iterará a coleção inteira.
Alex Siepman
então contar [ Count()] é basicamente diferente de iterar a lista inteira. Count()é pré-calculado, mas a iteração de toda a lista não é.
Jogi 02/02
@rehan khan: Eu não entendo a diferença entre Count () e Contagem ()
Alex Siepman
2
@RehanKhan: IsMultiple NÃO está fazendo um Count (), ele pára imediatamente após 2 itens. Assim como Take (2) .Count> = 2;
Alex Siepman
6

Eu criei uma extensão para responder a isso, você pode incluí-la em seus projetos, acho que isso retorna mais quando você procura por duplicatas na List ou no Linq.

Exemplo:

//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
fonte
1
Considere usar Skip (1) .Any () em vez de Count (). Se você tiver 1000 duplicatas, então Skip (1) .Any () será interrompido após encontrar a segunda. Count () acessará todos os 1000 elementos.
Harald Coppoolse 26/10
1
Se você adicionar esse método de extensão, considere usar HashSet.Add em vez de GroupBy, conforme sugerido em uma das outras respostas. Assim que o HashSet.Add encontrar uma duplicata, ele será interrompido. Seu GroupBy continuará agrupando todos os elementos, mesmo que um grupo com mais de um elemento tenha sido encontrado
Harald Coppoolse
6

Para encontrar apenas os valores duplicados:

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

Por exemplo. var list = new [] {1,2,3,1,4,2};

então agrupar por agrupará os números por suas teclas e manterá a contagem (número de vezes que repetiu) com ele. Depois disso, estamos apenas verificando os valores que se repetiram mais de uma vez.

Para encontrar apenas os valores uniuqe:

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

Por exemplo. var list = new [] {1,2,3,1,4,2};

então agrupar por agrupará os números por suas teclas e manterá a contagem (número de vezes que repetiu) com ele. Depois disso, estamos apenas verificando os valores que repetiram apenas uma vez que as médias são únicas.

LAV VISHWAKARMA
fonte
O código abaixo também encontrará itens exclusivos. var unique = list.Distinct(x => x)
Malu MN
1

Conjunto completo de extensões Linq para SQL das funções Duplicadas verificadas no MS SQL Server. Sem usar .ToList () ou IEnumerable. Essas consultas são executadas no SQL Server e não na memória. . Os resultados retornam apenas na memória.

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
fonte
0

há uma resposta, mas eu não entendi porque não está funcionando;

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

minha solução é assim nessa situação;

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