Pergunta

Alguém tem um método rápido para desduplicar uma lista genérica em C#?

Foi útil?

Solução

Talvez você deva considerar usar um HashSet.

No link do MSDN:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        HashSet<int> evenNumbers = new HashSet<int>();
        HashSet<int> oddNumbers = new HashSet<int>();

        for (int i = 0; i < 5; i++)
        {
            // Populate numbers with just even numbers.
            evenNumbers.Add(i * 2);

            // Populate oddNumbers with just odd numbers.
            oddNumbers.Add((i * 2) + 1);
        }

        Console.Write("evenNumbers contains {0} elements: ", evenNumbers.Count);
        DisplaySet(evenNumbers);

        Console.Write("oddNumbers contains {0} elements: ", oddNumbers.Count);
        DisplaySet(oddNumbers);

        // Create a new HashSet populated with even numbers.
        HashSet<int> numbers = new HashSet<int>(evenNumbers);
        Console.WriteLine("numbers UnionWith oddNumbers...");
        numbers.UnionWith(oddNumbers);

        Console.Write("numbers contains {0} elements: ", numbers.Count);
        DisplaySet(numbers);
    }

    private static void DisplaySet(HashSet<int> set)
    {
        Console.Write("{");
        foreach (int i in set)
        {
            Console.Write(" {0}", i);
        }
        Console.WriteLine(" }");
    }
}

/* This example produces output similar to the following:
 * evenNumbers contains 5 elements: { 0 2 4 6 8 }
 * oddNumbers contains 5 elements: { 1 3 5 7 9 }
 * numbers UnionWith oddNumbers...
 * numbers contains 10 elements: { 0 2 4 6 8 1 3 5 7 9 }
 */

Outras dicas

Se estiver usando .Net 3+, você pode usar o Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();

Que tal:-

var noDupes = list.Distinct().ToList();

Em .net 3.5?

Basta inicializar um HashSet com uma lista do mesmo tipo:

var noDupes = new HashSet<T>(withDupes);

Ou, se você quiser que uma lista seja retornada:

var noDupsList = new HashSet<T>(withDupes).ToList();

Classifique-o e marque dois e dois próximos um do outro, pois as duplicatas se aglomerarão.

Algo assim:

list.Sort();
Int32 index = list.Count - 1;
while (index > 0)
{
    if (list[index] == list[index - 1])
    {
        if (index < list.Count - 1)
            (list[index], list[list.Count - 1]) = (list[list.Count - 1], list[index]);
        list.RemoveAt(list.Count - 1);
        index--;
    }
    else
        index--;
}

Notas:

  • A comparação é feita de trás para frente, para evitar ter que recorrer à lista após cada remoção
  • Este exemplo agora usa C# Value Tuples para fazer a troca, substitua pelo código apropriado se você não puder usar isso
  • O resultado final não está mais classificado

Funcionou para mim.simplesmente use

List<Type> liIDs = liIDs.Distinct().ToList<Type>();

Substitua "Tipo" pelo tipo desejado, por exemplo.interno.

Eu gosto de usar este comando:

List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
                                                 .GroupBy(s => s.City)
                                                 .Select(grp => grp.FirstOrDefault())
                                                 .OrderBy(s => s.City)
                                                 .ToList();

Eu tenho esses campos na minha lista:ID, Storename, City, PostalCode Eu queria mostrar a lista de cidades em um suspensão que possui valores duplicados.solução:Agrupe por cidade e escolha a primeira da lista.

Espero que ajude :)

Como kronoz disse no .Net 3.5 você pode usar Distinct().

No .Net 2 você poderia imitá-lo:

public IEnumerable<T> DedupCollection<T> (IEnumerable<T> input) 
{
    var passedValues = new HashSet<T>();

    // Relatively simple dupe check alg used as example
    foreach(T item in input)
        if(passedValues.Add(item)) // True if item is new
            yield return item;
}

Isso pode ser usado para desduplicar qualquer coleção e retornará os valores na ordem original.

Normalmente é muito mais rápido filtrar uma coleção (já que ambos Distinct() e esta amostra faz) do que seria remover itens dela.

Um método de extensão pode ser um caminho decente a seguir ...algo assim:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
    return listToDeduplicate.Distinct().ToList();
}

E então ligue assim, por exemplo:

List<int> myFilteredList = unfilteredList.Deduplicate();

Em Java (presumo que C# seja mais ou menos idêntico):

list = new ArrayList<T>(new HashSet<T>(list))

Se você realmente quisesse alterar a lista original:

List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);

Para preservar a ordem, basta substituir HashSet por LinkedHashSet.

Use o Linq União método.

Observação:Esta solução não requer nenhum conhecimento de Linq, além de existir.

Código

Comece adicionando o seguinte ao topo do seu arquivo de classe:

using System.Linq;

Agora, você pode usar o seguinte para remover duplicatas de um objeto chamado, obj1:

obj1 = obj1.Union(obj1).ToList();

Observação:Renomear obj1 ao nome do seu objeto.

Como funciona

  1. O comando Union lista uma de cada entrada de dois objetos de origem.Como obj1 são ambos objetos de origem, isso reduz obj1 a uma de cada entrada.

  2. O ToList() retorna uma nova lista.Isso é necessário porque comandos Linq como Union retorna o resultado como um resultado IEnumerable em vez de modificar a Lista original ou retornar uma nova Lista.

Se você não se importa com a ordem, você pode simplesmente colocar os itens em um HashSet, se você fazer quiser manter a ordem, você pode fazer algo assim:

var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
    if (hs.Add(t))
        unique.Add(t);

Ou do jeito Linq:

var hs = new HashSet<T>();
list.All( x =>  hs.Add(x) );

Editar: O HashSet método é O(N) tempo e O(N) espaço enquanto classifica e torna único (conforme sugerido por @Lassevk e outros) é O(N*lgN) tempo e O(1) espaço, então não está tão claro para mim (como estava à primeira vista) que a forma de classificação é inferior (minhas desculpas pelo voto negativo temporário...)

Aqui está um método de extensão para remover duplicatas adjacentes in-situ.Chame Sort() primeiro e passe o mesmo IComparer.Isso deve ser mais eficiente que Lasse V.A versão de Karlsen que chama RemoveAt repetidamente (resultando em múltiplas movimentações de memória de bloco).

public static void RemoveAdjacentDuplicates<T>(this List<T> List, IComparer<T> Comparer)
{
    int NumUnique = 0;
    for (int i = 0; i < List.Count; i++)
        if ((i == 0) || (Comparer.Compare(List[NumUnique - 1], List[i]) != 0))
            List[NumUnique++] = List[i];
    List.RemoveRange(NumUnique, List.Count - NumUnique);
}

Como método auxiliar (sem Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}

Instalando o MaisLINQ pacote via Nuget, você pode facilmente distinguir a lista de objetos por uma propriedade

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 

Isso pega o distinto (os elementos sem elementos duplicados) e o converte em uma lista novamente:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();

Pode ser mais fácil simplesmente garantir que as duplicatas não sejam adicionadas à lista.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)

Outra maneira no .Net 2.0

    static void Main(string[] args)
    {
        List<string> alpha = new List<string>();

        for(char a = 'a'; a <= 'd'; a++)
        {
            alpha.Add(a.ToString());
            alpha.Add(a.ToString());
        }

        Console.WriteLine("Data :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t); });

        alpha.ForEach(delegate (string v)
                          {
                              if (alpha.FindAll(delegate(string t) { return t == v; }).Count > 1)
                                  alpha.Remove(v);
                          });

        Console.WriteLine("Unique Result :");
        alpha.ForEach(delegate(string t) { Console.WriteLine(t);});
        Console.ReadKey();
    }

Existem muitas maneiras de resolver - o problema de duplicatas na Lista, abaixo está uma delas:

List<Container> containerList = LoadContainer();//Assume it has duplicates
List<Container> filteredList = new  List<Container>();
foreach (var container in containerList)
{ 
  Container duplicateContainer = containerList.Find(delegate(Container checkContainer)
  { return (checkContainer.UniqueId == container.UniqueId); });
   //Assume 'UniqueId' is the property of the Container class on which u r making a search

    if(!containerList.Contains(duplicateContainer) //Add object when not found in the new class object
      {
        filteredList.Add(container);
       }
  }

Saúde Ravi Ganesan

Aqui está uma solução simples que não requer nenhum LINQ difícil de ler ou qualquer classificação prévia da lista.

   private static void CheckForDuplicateItems(List<string> items)
    {
        if (items == null ||
            items.Count == 0)
            return;

        for (int outerIndex = 0; outerIndex < items.Count; outerIndex++)
        {
            for (int innerIndex = 0; innerIndex < items.Count; innerIndex++)
            {
                if (innerIndex == outerIndex) continue;
                if (items[outerIndex].Equals(items[innerIndex]))
                {
                    // Duplicate Found
                }
            }
        }
    }

A resposta de David J. é um bom método, sem necessidade de objetos extras, classificação, etc.No entanto, pode ser melhorado:

for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)

Portanto, o loop externo vai para cima e para baixo em toda a lista, mas o loop interno vai para baixo "até que a posição do loop externo seja alcançada".

O loop externo garante que toda a lista seja processada, o loop interno encontra as duplicatas reais, isso só pode acontecer na parte que o loop externo ainda não processou.

Ou se você não quiser fazer o loop interno de baixo para cima, você pode fazer com que o loop interno comece em outerIndex + 1.

Você pode usar União

obj2 = obj1.Union(obj1).ToList();

Se você tem aulas de reboque Product e Customer e queremos remover itens duplicados da lista deles

public class Product
{
    public int Id { get; set; }
    public string ProductName { get; set; }

}

public class Customer
{
    public int Id { get; set; }
    public string CustomerName { get; set; }

}

Você deve definir uma classe genérica no formulário abaixo

public class ItemEqualityComparer<T> : IEqualityComparer<T> where T : class
{
    private readonly PropertyInfo _propertyInfo;

    public ItemEqualityComparer(string keyItem)
    {
        _propertyInfo = typeof(T).GetProperty(keyItem, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
    }

    public bool Equals(T x, T y)
    {
        var xValue = _propertyInfo?.GetValue(x, null);
        var yValue = _propertyInfo?.GetValue(y, null);
        return xValue != null && yValue != null && xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        var propertyValue = _propertyInfo.GetValue(obj, null);
        return propertyValue == null ? 0 : propertyValue.GetHashCode();
    }
}

então, você pode remover itens duplicados da sua lista.

var products = new List<Product>
            {
                new Product{ProductName = "product 1" ,Id = 1,},
                new Product{ProductName = "product 2" ,Id = 2,},
                new Product{ProductName = "product 2" ,Id = 4,},
                new Product{ProductName = "product 2" ,Id = 4,},
            };
var productList = products.Distinct(new ItemEqualityComparer<Product>(nameof(Product.Id))).ToList();

var customers = new List<Customer>
            {
                new Customer{CustomerName = "Customer 1" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
                new Customer{CustomerName = "Customer 2" ,Id = 5,},
            };
var customerList = customers.Distinct(new ItemEqualityComparer<Customer>(nameof(Customer.Id))).ToList();

este código remove itens duplicados por Id se quiser remover itens duplicados por outra propriedade, você pode alterar nameof(YourClass.DuplicateProperty) mesmo nameof(Customer.CustomerName) em seguida, remova itens duplicados CustomerName Propriedade.

  public static void RemoveDuplicates<T>(IList<T> list )
  {
     if (list == null)
     {
        return;
     }
     int i = 1;
     while(i<list.Count)
     {
        int j = 0;
        bool remove = false;
        while (j < i && !remove)
        {
           if (list[i].Equals(list[j]))
           {
              remove = true;
           }
           j++;
        }
        if (remove)
        {
           list.RemoveAt(i);
        }
        else
        {
           i++;
        }
     }  
  }

Uma implementação simples e intuitiva:

public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
    List<PointF> result = new List<PointF>();

    for (int i = 0; i < listPoints.Count; i++)
    {
        if (!result.Contains(listPoints[i]))
            result.Add(listPoints[i]);
        }

        return result;
    }

Todas as respostas copiam listas, ou criam uma nova lista, ou usam funções lentas, ou são dolorosamente lentas.

No meu entender, este é o método mais rápido e barato Eu sei (também apoiado por um programador muito experiente, especializado em otimização física em tempo real).

// Duplicates will be noticed after a sort O(nLogn)
list.Sort();

// Store the current and last items. Current item declaration is not really needed, and probably optimized by the compiler, but in case it's not...
int lastItem = -1;
int currItem = -1;

int size = list.Count;

// Store the index pointing to the last item we want to keep in the list
int last = size - 1;

// Travel the items from last to first O(n)
for (int i = last; i >= 0; --i)
{
    currItem = list[i];

    // If this item was the same as the previous one, we don't want it
    if (currItem == lastItem)
    {
        // Overwrite last in current place. It is a swap but we don't need the last
       list[i] = list[last];

        // Reduce the last index, we don't want that one anymore
        last--;
    }

    // A new item, we store it and continue
    else
        lastItem = currItem;
}

// We now have an unsorted list with the duplicates at the end.

// Remove the last items just once
list.RemoveRange(last + 1, size - last - 1);

// Sort again O(n logn)
list.Sort();

O custo final é:

nlogn + n + nlogn = n + 2nlogn = O(nlogn) o que é muito bom.

Nota sobre RemoveRange:Como não podemos definir a contagem da lista e evitar o uso das funções Remove, não sei exatamente a velocidade desta operação, mas acho que é o caminho mais rápido.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top