¿Alguien tiene un método rápido para desduplicar una Lista genérica en C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
¿Alguien tiene un método rápido para desduplicar una Lista genérica en C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Respuestas:
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 }
*/
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 ListView
en modo virtual. Fue súper rápido hacer un HashSet<>
primero y luego convertirlo en un List<>
(para que ListView
pueda acceder a los elementos por índice). List<>.Contains()
es muy lento
Si está usando .Net 3+, puede usar Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
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();
List<T>
resultado como usonew 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:
RemoveAt
es una operación muy costosa en unList
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 :)
Funcionó para mi. simplemente use
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
Reemplace "Tipo" con su tipo deseado, por ejemplo, int.
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.
HashSet
constructor dedujo, lo que lo hace mejor para la mayoría de las circunstancias. Sin embargo, esto preservaría el orden de clasificación, que HashSet
no lo hace.
Dictionary<T, object>
lugar, reemplazar .Contains
con .ContainsKey
y .Add(item)
con.Add(item, null)
HashSet
conserva el orden mientras Distinct()
que no.
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();
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.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Esto toma distintos (los elementos sin elementos duplicados) y los convierte nuevamente en una lista:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
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 obj1
el nombre al nombre de su objeto.
Cómo funciona
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.
El ToList()
devuelve una nueva lista. Esto es necesario, porque los comandos de Linq como Union
devuelven el resultado como un resultado IEnumerable en lugar de modificar la Lista original o devolver una nueva Lista.
Como método auxiliar (sin Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
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 HashSet
mé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 ...)
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);
}
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);
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)
List<T>.Contains
mé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.
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 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
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, 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.
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.
Si tiene clases de remolque Product
y Customer
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; }
}
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 Id
si desea eliminar elementos duplicados por otros bienes, que puede cambiar nameof(YourClass.DuplicateProperty)
misma nameof(Customer.CustomerName)
a continuación, eliminar elementos duplicados de CustomerName
la 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;
}