Pregunta

¿Alguien tiene un método rápido para eliminar duplicados de una lista genérica en C#?

¿Fue útil?

Solución

Quizás deberías considerar usar un Conjunto de hash.

Desde el enlace de 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 }
 */

Otros consejos

Si está utilizando .Net 3+, puede usar Linq.

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

Qué tal si:-

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

¿En .net 3.5?

Simplemente inicialice un HashSet con una Lista del mismo tipo:

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

O, si desea que se devuelva una Lista:

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

Ordénelo, luego marque dos y dos uno al lado del otro, ya que los duplicados se agruparán.

Algo como esto:

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:

  • La comparación se realiza de atrás hacia adelante, para evitar tener que recurrir a la lista después de cada eliminación.
  • Este ejemplo ahora usa C# Value Tuples para realizar el intercambio; sustitúyalo con el código apropiado si no puede usarlo
  • El resultado final ya no está ordenado.

Funcionó para mí.simplemente usa

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

Reemplace "Tipo" con el tipo que desee, p.En t.

Me gusta usar este comando:

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

Tengo estos campos en mi lista:ID, Storename, City, Postalcode Quería mostrar la lista de ciudades en un menú desplegable que tiene valores duplicados.solución:Agrupe por ciudad y luego elija la primera de la lista.

Espero que ayude :)

Como dijo kronoz en .Net 3.5 puedes usar Distinct().

En .Net 2 podrías imitarlo:

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

Esto podría usarse para deduplicar cualquier colección y devolverá los valores en el orden original.

Normalmente es mucho más rápido filtrar una colección (ya que ambos Distinct() y este ejemplo lo hace) que eliminar elementos del mismo.

Un método de extensión podría ser una buena manera de hacerlo...algo como esto:

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

Y luego llama así, por ejemplo:

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

En Java (supongo que C# es más o menos idéntico):

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

Si realmente quisieras mutar la lista original:

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

Para preservar el orden, simplemente reemplace HashSet con LinkedHashSet.

Utilice Linq Unión método.

Nota:Esta solución no requiere ningún conocimiento de Linq, aparte de que existe.

Código

Comience agregando lo siguiente en la parte superior de su archivo de clase:

using System.Linq;

Ahora, puede usar lo siguiente para eliminar duplicados de un objeto llamado, obj1:

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

Nota:Rebautizar obj1 al nombre de su objeto.

Cómo funciona

  1. El comando Unión enumera una de cada entrada de dos objetos de origen.Dado que obj1 son ambos objetos fuente, esto reduce obj1 a uno de cada entrada.

  2. El ToList() devuelve una nueva Lista.Esto es necesario, porque los comandos de Linq como Union devuelve el resultado como un resultado IEnumerable en lugar de modificar la Lista original o devolver una nueva Lista.

Si no le importa el orden, puede simplemente colocar los artículos en un HashSet, si usted hacer Si quieres mantener el orden puedes hacer algo como esto:

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

O a la manera Linq:

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

Editar: El HashSet el método es O(N) tiempo y O(N) espacio mientras clasifica y luego lo hace único (como lo sugiere @lassevk y otros) es O(N*lgN) tiempo y O(1) espacio, por lo que no me resulta tan claro (como lo fue a primera vista) que la forma de clasificación sea inferior (mis disculpas por el voto negativo temporal...)

A continuación se muestra un método de extensión para eliminar duplicados adyacentes in situ.Llame a Sort() primero y pase el mismo IComparer.Esto debería ser más eficiente que Lasse V.La versión de Karlsen que llama a RemoveAt repetidamente (lo que resulta en múltiples movimientos de memoria de bloque).

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 (sin Linq):

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

Instalación del MásLINQ paquete a través de Nuget, puede distinguir fácilmente la lista de objetos por una propiedad

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

Esto toma distintos (los elementos sin elementos duplicados) y los convierte nuevamente en una lista:

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

Podría ser más fácil simplemente asegurarse de que no se agreguen duplicados a la lista.

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

Otra forma en .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();
    }

Hay muchas maneras de resolverlo: el problema de los duplicados en la Lista, a continuación se muestra una de ellas:

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

Saludos Ravi Ganesan

Aquí hay una solución simple que no requiere ningún LINQ difícil de leer ni ninguna clasificación previa de la 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
                }
            }
        }
    }

La respuesta de David J. es un buen método, no hay necesidad de objetos adicionales, clasificación, etc.Se puede mejorar sin embargo:

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

Entonces, el bucle externo va de arriba a abajo para toda la lista, pero el bucle interno va hacia abajo "hasta que se alcanza la posición del bucle externo".

El bucle externo se asegura de que se procese toda la lista, el bucle interno encuentra los duplicados reales, que solo pueden ocurrir en la parte que el bucle externo aún no ha procesado.

O si no desea hacer de abajo hacia arriba para el bucle interno, puede hacer que el bucle interno comience en externalIndex + 1.

Puedes usar Unión

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

Si tienes clases de remolque Product y Customer y queremos eliminar elementos duplicados de su lista

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

}

Debes definir una clase genérica en el siguiente formulario.

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

luego, puede eliminar elementos duplicados en su 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 elimina elementos duplicados mediante Id Si desea eliminar elementos duplicados de otra propiedad, puede cambiar nameof(YourClass.DuplicateProperty) mismo nameof(Customer.CustomerName) luego elimine los elementos duplicados haciendo CustomerName Propiedad.

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

Una implementación intuitiva simple:

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 las respuestas copian listas, crean una nueva lista, usan funciones lentas o simplemente son tremendamente lentas.

A mi entender, este es el método más rápido y más barato Lo sé (además, respaldado por un programador muy experimentado especializado en optimización de física en tiempo 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();

El costo final es:

nlogn + n + nlogn = n + 2nlogn = O (iniciar sesión) lo cual es bastante lindo.

Nota sobre RemoveRange:Como no podemos establecer el recuento de la lista y evitar el uso de las funciones Eliminar, no sé exactamente la velocidad de esta operación, pero supongo que es la forma más rápida.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top