Eliminar duplicados de una Lista <T> en C #

487

¿Alguien tiene un método rápido para desduplicar una Lista genérica en C #?

JC Grubbs
fuente
44
¿Te importa el orden de los elementos en el resultado? Esto excluirá algunas soluciones.
Coronel Panic
Una solución de una línea:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse

Respuestas:

227

Quizás debería considerar usar un HashSet .

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 }
 */
Jason Baker
fuente
11
es increíblemente rápido ... ¡100,000 cadenas con List requieren 400s y 8MB de ram, mi propia solución toma 2.5s y 28MB, hashset toma 0.1s! y 11MB de ram
sasjaq
3
HashSet no tiene un índice , por lo tanto, no siempre es posible usarlo. Tengo que crear una vez una gran lista sin duplicados y luego usarla ListViewen modo virtual. Fue súper rápido hacer un HashSet<>primero y luego convertirlo en un List<>(para que ListViewpueda acceder a los elementos por índice). List<>.Contains()es muy lento
Sinatr
58
Ayudaría si hubiera un ejemplo de cómo usar un hashset en este contexto particular.
Nathan McKaskle
23
¿Cómo puede esto considerarse una respuesta? Es un enlace
mcont
2
HashSet es excelente en la mayoría de las circunstancias. Pero si tiene un objeto como DateTime, se compara por referencia y no por valor, por lo que aún terminará con duplicados.
Jason McKindly
813

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

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Factor místico
fuente
14
Ese código fallará ya que .Distinct () devuelve un IEnumerable <T>. Tienes que agregarle .ToList ().
ljs
Este enfoque solo se puede usar para listas con valores simples.
Polaris
20
No, funciona con listas que contienen objetos de cualquier tipo. Pero tendrá que anular el comparador predeterminado para su tipo. Así: public override bool Equals (object obj) {...}
BaBu
1
Siempre es una buena idea anular ToString () y GetHashCode () con sus clases para que este tipo de cosas funcione.
B Seven
2
También puede usar el paquete MoreLinQ Nuget que tiene un método de extensión .DistinctBy (). Bastante útil
yu_ominae
178

Qué tal si:

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

¿En .net 3.5?

ljs
fuente
¿Duplica la lista?
darkgaze
1
@darkgaze esto solo crea otra lista con solo entradas únicas. Por lo tanto, se eliminarán los duplicados y quedará con una lista donde cada posición tiene un objeto diferente.
hexagod
¿
Funciona
90

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();
Incluso Mien
fuente
3
... y si necesita un List<T>resultado como usonew HashSet<T>(withDupes).ToList()
Tim Schmelter
47

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 Tuplas de valor C # para hacer el intercambio, sustitúyalo con el código apropiado si no puede usar eso
  • El resultado final ya no está ordenado
Lasse V. Karlsen
fuente
1
Si no me equivoco, la mayoría de los enfoques mencionados anteriormente son solo abstracciones de estas mismas rutinas, ¿verdad? Habría adoptado tu enfoque aquí, Lasse, porque así es como imagino mentalmente el movimiento a través de los datos. Pero, ahora estoy interesado en las diferencias de rendimiento entre algunas de las sugerencias.
Ian Patrick Hughes
77
Impleméntelos y cronometrelos, la única forma de estar seguro. Incluso la notación Big-O no lo ayudará con las métricas de rendimiento reales, solo una relación de efecto de crecimiento.
Lasse V. Karlsen
1
Me gusta este enfoque, es más portátil a otros idiomas.
Jerry Liang
10
No hagas eso. Es super lento. RemoveAtes una operación muy costosa en unList
Clément
1
Clément tiene razón. Una forma de salvar esto sería envolver esto en un método que rinda con un enumerador y solo devuelva valores distintos. Alternativamente, puede copiar valores a una nueva matriz o lista.
JHubbard80
33

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, Ciudad, Código postal. Quería mostrar la lista de ciudades en un menú desplegable que tiene valores duplicados. solución: Agrupar por ciudad y luego elegir el primero para la lista.

Espero que ayude :)

Eric
fuente
31

Funcionó para mi. simplemente use

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

Reemplace "Tipo" con su tipo deseado, por ejemplo, int.

Hossein Sarshar
fuente
1
Distintivo está en Linq, no en System.Collections.Generic según lo informado por la página de MSDN.
Almo
55
¿Esta respuesta (2012) parece ser la misma que otras dos respuestas en esta página que son de 2008?
Jon Schneider
23

Como dijo kronoz en .Net 3.5, puede usarlo Distinct().

En .Net 2 puedes 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 deducir cualquier colección y devolverá los valores en el orden original.

Normalmente es mucho más rápido filtrar una colección (como ambos Distinct()y esta muestra) que eliminar elementos de ella.

Keith
fuente
Sin embargo, el problema con este enfoque es que es O (N ^ 2) -ish, en oposición a un hashset. Pero al menos es evidente lo que está haciendo.
Tamas Czinege
1
@DrJokepu: en realidad no me di cuenta de que el HashSetconstructor dedujo, lo que lo hace mejor para la mayoría de las circunstancias. Sin embargo, esto preservaría el orden de clasificación, que HashSetno lo hace.
Keith el
1
HashSet <T> se introdujo en 3.5
thorn̈
1
@thorn realmente? Tan difícil de seguir. En ese caso, podría usar un Dictionary<T, object>lugar, reemplazar .Containscon .ContainsKeyy .Add(item)con.Add(item, null)
Keith
@Keith, según mis pruebas HashSetconserva el orden mientras Distinct()que no.
Dennis T --Reinstalar a Monica--
13

Un método de extensión podría ser un camino decente ... algo como esto:

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

Y luego llame así, por ejemplo:

List<int> myFilteredList = unfilteredList.Deduplicate();
Geoff Taylor
fuente
11

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

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

Si realmente quería 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.

Tom Hawtin - tackline
fuente
55
en C # sería: List <T> noDupes = new List <T> (new HashSet <T> (list)); list.Clear (); list.AddRange (noDupes);
smohamed
En C #, es más fácil de esta manera: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal
10

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

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Alfred Udah
fuente
9

Utilice el método de unión de Linq .

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

Código

Comience agregando lo siguiente a 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: Cambie obj1el nombre al nombre de su objeto.

Cómo funciona

  1. El comando Unión enumera una de cada entrada de dos objetos de origen. Como obj1 es ambos objetos fuente, esto reduce obj1 a una de cada entrada.

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

WonderWorker
fuente
7

Como método auxiliar (sin Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}
Conceder
fuente
Creo que Distinct ya está ocupado. Aparte de eso (si cambia el nombre del método) debería funcionar.
Andreas Reiff
6

Si no se preocupan por el orden que sólo puede empujar los objetos en una HashSet, si no desea mantener el orden en el que puede 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 la forma de Linq:

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

Editar: El HashSetmétodo es O(N)tiempo y O(N)espacio mientras se ordena y luego se hace único (como lo sugirieron @ lassevk y otros) es O(N*lgN)tiempo y O(1)espacio, por lo que no es tan claro para mí (como lo fue a primera vista) que la forma de clasificación es inferior (mi disculpas por el voto negativo temporal ...)

Motti
fuente
6

Aquí hay un método de extensión para eliminar duplicados adyacentes in situ. Llame primero a Sort () y pase en el mismo IComparer. Esto debería ser más eficiente que la versión de Lasse V. 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);
}
Gary
fuente
5

Al instalar el paquete MoreLINQ a través de Nuget, puede distinguir fácilmente la lista de objetos por una propiedad

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

Puede 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)
Chris
fuente
1
Actualmente lo estoy haciendo así, pero cuantas más entradas tenga, más tardará la comprobación de duplicados.
Robert Strauch
Tengo el mismo problema aquí. Estoy usando el List<T>.Containsmétodo cada vez pero con más de 1,000,000 de entradas. Este proceso ralentiza mi solicitud. Estoy usando un List<T>.Distinct().ToList<T>()primero en su lugar.
RPDeshaies
Este método es muy lento
darkgaze
3

Puedes usar Union

obj2 = obj1.Union(obj1).ToList();
flagamba
fuente
77
Explicación de por qué funcionaría definitivamente mejoraría esta respuesta
Igor B
2

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

Hay muchas formas de resolver: el problema de los duplicados en la Lista, a continuación, es uno de ellos:

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

Ravi Ganesan
fuente
2

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
                }
            }
        }
    }
David J.
fuente
Tiene más control sobre los elementos duplicados con este método. Aún más si tiene una base de datos para actualizar. Para innerIndex, ¿por qué no comenzar desde externalIndex + 1 en lugar de comenzar desde siempre?
Nolmë Informatique
2

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

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

Por lo tanto, el bucle externo va en la parte superior inferior de toda la lista, pero el bucle interno va en la parte inferior "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, eso solo puede suceder en la parte que el bucle externo aún no ha procesado.

O si no desea hacer una búsqueda ascendente del bucle interno, puede hacer que el bucle interno comience en externalIndex + 1.

Invitado
fuente
2

Todas las respuestas copian listas, o crean una nueva lista, o usan funciones lentas, o son dolorosamente lentas.

Según tengo entendido, este es el método más rápido y económico que conozco (también, respaldado por un programador muy experimentado especializado en la optimización 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 (nlogn) lo cual es bastante bueno.

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

mirada oscura
fuente
2

Si tiene clases de remolque Producty Customerqueremos 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; }

}

Debe 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 de 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 quitar elementos duplicados por Idsi desea eliminar elementos duplicados por otros bienes, que puede cambiar nameof(YourClass.DuplicateProperty) misma nameof(Customer.CustomerName)a continuación, eliminar elementos duplicados de CustomerNamela propiedad.

Reza Jenabi
fuente
1
  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++;
        }
     }  
  }
Paul Richards
fuente
1

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;
    }
Moctar Haiz
fuente
Este método también es lento. Crea una nueva lista.
darkgaze