C# - Encontrar itens duplicados em uma lista
 Neste artigo vou apresentar algumas fomas de encontrar itens duplicados em uma lista.


Hoje nosso objetivo será retornar ou contar o número de ocorrências de um objeto contido em uma lista List<T>que atenda a um critério de busca.
 


 

Vamos usar as seguintes abordagens:

  1. Criar métodos de extensão para a Lista

    • Criar o método de extensão GetTodos() que retorna todos os objetos que atendam ao critério de busca em uma lista ordenada ou não ordenada;

    • Criar o método de extensão BinarySearchGetTodos() que retorna todos os objetos que atendem o critério em uma lista ordenada;

    • Criar o método ContaTodos() que conta o número de vezes que um item aparece na lista ordenada ou não;

    • Criar o método BinarySearchContaTodos() que conta o número de vezes que um item aparece na lista ordenada;
       

  2. Usar LINQ

    • Usar o método GroupBy()

    • Usar o Set

Vamos iniciar ...

 

Criando métodos de extensão para a lista

 

Vamos criar um projeto Console para .NET Core usando o VS 2022 no ambiente do .NET 8.
 

Vamos criar uma classe chamada CollectionExtensionMethods onde vamos implementar 4 métodos de extensão

 

static class CollectionExtensionMethods
{
    // retorna todos objetos que atendemo critério
    // em uma lista List<T> ordenada ou não ordenada

    public static IEnumerable<T> GetTodos<T>(this List<T> minhaLista, T valor) =>
                                           minhaLista.Where(t => t.Equals(valor));

    // Retorna todos os objetos que atendem o critério na lista ordenada
    public static T[] BinarySearchGetTodos<T>(this List<T> minhaLista, T valor)
    {
        List<T> retornaObjs = new List<T>();

        // procura pelo primeiro item
        int centro = minhaLista.BinarySearch(valor);
        if (centro > 0)
        {
            retornaObjs.Add(minhaLista[centro]);
            int esquerda = centro;
            while (esquerda > 0 && minhaLista[esquerda - 1].Equals(valor))
            {
                esquerda -= 1;
                retornaObjs.Add(minhaLista[esquerda]);
            }
            int direita = centro;
            while (direita < (minhaLista.Count - 1) &&
            minhaLista[direita + 1].Equals(valor))
            {
                direita += 1;
                retornaObjs.Add(minhaLista[direita]);
            }
        }
        return (retornaObjs.ToArray());
    }

    // Conta o número de vezes que um item aparece
    // na lista ordenada ou não ordenada

    public static int ContaTodos<T>(this List<T> minhaLista, T valor) =>
                                    minhaLista.GetTodos(valor).Count();

    // Conta o número de vezes que um item aparece na lista ordenada
    public static int BinarySearchContaTodos<T>(this List<T> minhaLista, T valor) =>
                                               BinarySearchGetTodos(minhaLista, valor).Count();

}

 

A principal diferença entre os métodos GetTodos() e BinarySearchGetTodos() e se a busca será feita em uma lista ordenada ou não ordenada.

 

Os métodos BinarySearchGetTodos() e BinarySearchContaTodos() usam o método BinarySearch que usa um algoritmo de pesquisa binária para localizar um elemento específico em uma List<T>classificado ou parte dele.
 

A busca/pesquisa binária é um algoritmo que implementa o paradigma Divisão e Conquista para encontrar um elemento em uma lista ordenada. Uma analogia ao funcionamento desse algoritmo seria a busca de uma palavra em dicionário.

 

Vamos fazer um teste usando o método GetTodos() em uma lista usando o código abaixo:

 

using ProcuraItensDuplicados.Extensions;

List<int> listaRetorno = new List<int>() { -1, -1, 1, 2, 2, 2, 2, 3, 100, 78, 14, 2, 4, 5 };
int numero = 0;
Console.WriteLine("-- Verificar número duplicado na lista --\n");

do
{
    Console.WriteLine("Informe o numero :");
    numero = Convert.ToInt16(Console.ReadLine());

    IEnumerable<int> items = listaRetorno.GetTodos(numero);

    if (items.Count() > 0)
    {
        Console.WriteLine($"Existem {items.Count()} valores igual a {numero} na lista \n");
        foreach (var item in items)
        {
            Console.WriteLine($"item  : {item}");
        }
    }
    else
      Console.WriteLine($"\nNão existem valores duplicados na lista para o numero {numero}");
}
while(numero != 888);

Console.WriteLine("\n ### Fim ### ");
Console.ReadKey();

Criamos uma lista de inteiros e a seguir podemos informar um número e verificar se ele existe na lista e se esta duplicado.

Executando o projeto podemos ter o seguinte resultado:

 

 

Obtemos como resultado um IEnumerable contendo os números duplicados.

 

Usando o método GroupBy da LINQ

 

Nesta abordagem a idéia é usar o método Enumerable.GroupBy() para agrupar os elementos com base em seu valor, filtrar os grupos que aparecem mais de uma vez e recuperar as chaves duplicadas.

 

Veja como ficaria o código:

 

Console.WriteLine("-- Usando Enumerable.GroupBy--  \n");
    Console.WriteLine("-- Verificar número duplicado na lista --\n");

    List<int> lista = new List<int>() { 5, -1, 4, 5, 9, -1, 8 , 7, -4, 15, 19, 2, 9};

    List<int> resultado = lista.GroupBy(x => x)
                                 .Where(g => g.Count() > 1)
                                 .Select(x => x.Key)
                                 .ToList();

    Console.WriteLine("Números duplicados na lista");
    Console.WriteLine(String.Join(", ", resultado));

    Console.WriteLine("\nlista:\n");
    foreach (var n in lista)
    {
        Console.Write($" {n}");
    }

    Console.ReadLine();

 

 

Neste exemplo , dada uma lista, será retornado os números duplicados.

 

Resultado da execução :

 

 

Podemos usar também o método Enumerable.SelectMany(). No entanto, para obter todas as duplicatas distintas, considere aplicar o método Distinct() à sequência resultante.

 

 List<int> lista = new List<int>() { 5, -1, 4, 5, 9, -1, 8, 5, 4, 7 };

    var duplicados = lista.GroupBy(x => x)
                            .SelectMany(g => g.Skip(1))
                           .Distinct()
                           .ToList();

    Console.WriteLine("Números duplicados na lista");
    Console.WriteLine(String.Join(", ", duplicados));

    Console.WriteLine("\nlista:\n");
    foreach (var n in lista)
    {
        Console.Write($" {n}");
    }
    Console.ReadLine();

 

 

Neste cenário é que entra em cena o padrão Null Object.

 

Usando Set

 

Usando outra abordagem podemos usar Set onde a ideia  é iterar sobre a lista e acompanhar todos os itens em um HashSet. Se um item for encontrado antes, marque-o como duplicado e relate todos os itens duplicados no final do loop.

 

List<int> lista = new List<int>() { 5, -1, 4, 5, 9, -1, 8, 4 };

    var conjunto = new HashSet<int>();

    var duplicados = lista.Where(x => !conjunto.Add(x));

    Console.WriteLine("Números duplicados na lista");
    Console.WriteLine(String.Join(", ", duplicados));

    Console.WriteLine("\nlista:\n");
    foreach (var n in lista)
    {
        Console.Write($" {n}");
    }
    Console.ReadLine();

 

 

Temos assim algumas abordagens que podemos usar para verificar se existem números duplicados em uma lista.

 

Pegue o código aqui:  ProcuraItensDuplicados.zip

 

"Ele (Jesus) deu a si mesmo por nós, a fim de nos remir de toda iniquidade e purificar, para si mesmo, um povo exclusivamente seu, dedicado à prática de boas obras."
Tito 2:14

 

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti